🪟

UIDesignRequiresCompatibility を変更して、Liquid Glass をランタイムで切り替える

に公開

はじめに

どうも tattsun です 🙌

「クラシルリワード」の名称が 「レシチャレ」 に変わりました 🎉
これからより一層、ユーザー価値を届けていけるように開発に取り組んでいこうと思います!

https://prtimes.jp/main/html/rd/p/000000413.000019382.html

Liquid Glass を無効にする方法

無効にするのは、とても簡単で Info.plistUIDesignRequiresCompatibilityYES にするだけです。

<key>UIDesignRequiresCompatibility</key>
<true/>

https://developer.apple.com/documentation/BundleResources/Information-Property-List/UIDesignRequiresCompatibility

ただし、この設定が利用可能なのは次のメジャーリリースまでのようです。

WWDC2025

https://developer.apple.com/videos/play/wwdc2025/102/?time=1153

UIDesignRequiresCompatibility を動的に切り替える

上記の通り、いつかは対応しなければいけないということでこの残された時間で計画的に対応が必要になります。そこで、開発や確認がしやすいように動的に Liquid Glass を切り替えられるようにしようというのが今回の目的です。

基本的には、こちらの記事を参考にさせていただきました 🙇

https://zenn.dev/tdkn/articles/57b536c192a292

swizzling で Info.plist の値を書き換える

UserDefaults に保持している値を参照して、UIDesignRequiresCompatibility を書き換えます。

extension Bundle {
    static func swizzleInfoDictionary() {
        guard #available(iOS 26.0, *) else { return }

        let originalSelector = #selector(getter: infoDictionary)
        let swizzledSelector = #selector(getter: swizzledInfoDictionary)

        guard let original = class_getInstanceMethod(Self.self, originalSelector),
                let swizzled = class_getInstanceMethod(Self.self, swizzledSelector)
        else { return }

        method_exchangeImplementations(original, swizzled)
    }

    @objc private var swizzledInfoDictionary: NSDictionary? {
        let dict = NSMutableDictionary(
            dictionary: self.swizzledInfoDictionary ?? [:]
        )

        dict["UIDesignRequiresCompatibility"] = UserDefaults.standard.bool(forKey: "isLiquidGlassDisabled")

        return dict
    }
}

アプリ起動時に反映する

AppDelegate で Info.plist の書き換え処理を呼び出します。
これで表示の切り替えが UserDefaults の値で出来るようになりました 🎉

func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
    #if DEBUG
        // ✅ swizzling を利用して、Liquid Glass の設定を書き換える
        // window が初期化される前に実行する必要がある
        Bundle.swizzleInfoDictionary()
    #endif
    
    // ❌ UIWindow を初期化した後だと読み込まれなかった
    self.window = UIWindow()
    
    // setup ...
}

なぜ UIWindow の初期化に左右されるのか

検証用のコードを用意して、挙動を確認してみる。

private func fetchLiquidGlassOption() -> String {
    if let value = Bundle.main.object(forInfoDictionaryKey: "UIDesignRequiresCompatibility") as? Bool {
        value ? "YES" : "NO"
    } else {
        "UNKNOWN"
    }
}

func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {

    let before = fetchLiquidGlassOption()

    window = UIWindow()
    #if DEBUG
        // swizzling を利用して、Liquid Glass の設定を書き換える
        // UIWindow が初期化された時点で Liquid Glass の設定値が反映されるため
        // UIWindow が初期化される前に実行する必要がある
        Bundle.swizzleInfoDictionary()
    #endif

    let after = fetchLiquidGlassOption()

    print("🚀 isLiquidGlassEnabled: \(!UserDefaults.standard.bool(forKey: "isLiquidGlassDisabled"))")
    print("🚀 [Before] UIDesignRequiresCompatibility: \(before)")
    print("🚀 [After] UIDesignRequiresCompatibility: \(after)")

    // setup ...
}

上記のようなコードを書いても Bundle から取得する Info.plist の値は処理の順番に関わらず不変(設定値が維持されている状態)だった。つまり、swizzling で置き換えている infoDictionaryInfo.plist とは別物ということがわかる。

  • infoDictionary: 内部で利用するためのプロパティ。Info.plist の値を読み込んでいる。

https://developer.apple.com/documentation/foundation/bundle/infodictionary

  • Bundle.main.object(forInfoDictionaryKey:): Info.plist の値を取得する

https://developer.apple.com/documentation/foundation/bundle/object(forinfodictionarykey:)

順番的には、以下のようになっていると推測している。

  1. Bundle.infoDictionaryInfo.plist の設定値が読み込まれる
  2. UIWindow が初期化されるタイミングで Bundle.infoDictionaryUIDesignRequiresCompatibility を参照して、Liquid Glass を適用するかを決定

ChatGPT など自分でもこの動作の裏付けの調査を行いましたが、特に情報はありませんでした… 😭

おわりに

設定値を変更した後に再起動が必要ですが、ビルドし直す必要がなくなったため開発が捗りそうです!
次回は、Xcode 26 対応の全体像についても記事を書こうと思っています ✨

Kurashiru Tech Blog

Discussion