有没有办法使用通知而不是标准的委托功能来观察 iOS 上暗模式和亮模式之间的变化?

问题描述

在 iOS 上检查暗模式和亮模式之间变化的标准方法是使用视图级委托功能

override func traitCollectionDidChange(_ prevIoUsTraitCollection: uitraitcollection?) {
    super.traitCollectionDidChange(prevIoUsTraitCollection)

    switch self.traitCollection.userInterfaceStyle {
    case .dark:
        print("dark")
    case .light:
        print("light")
    case .unspecified:
        print("unspecified")
    @unkNown default:
        fatalError()
    }
}

这很有效,但在一个拥有 100 个视图控制器的应用程序中,将这个调用添加到每个控制器是一件很麻烦的事情,而且很混乱。我想要做的是通过 NotificationCenter 观察 AppDelegate 级别上的明暗变化。这样我就可以制作一个适用于所有视图的全局主题更改功能

我在 AppDelegate.swift 中尝试了以下操作,但在暗/亮之间切换时从未调用 daytimeDidChange:

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

    NotificationCenter.default.addobserver(self,selector: #selector(daytimeDidChange),name: nil,object: UIScreen.main.traitCollection)

}

@objc func daytimeDidChange() {
     if uitraitcollection.current.userInterfaceStyle == .dark {
         //Dark
         print("dark")
     }
     else {
         //Light
         print("light")
     }
}

关于如何正确设置通知观察者的任何想法?

解决方法

让我们看看我们有什么。我写了一个完整的答案,所以如果你想跳过第一段。

标准解决方案 - 正如您提到的 - 将在每个(至少在某些)UIView / UIViewController 中实现此方法:

override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
    super.traitCollectionDidChange(previousTraitCollection)
    print("isDark: \(UITraitCollection.current.userInterfaceStyle == .dark)")
}

如果这不符合您的需求,那么这里是另一种解决方案,根据您的需要,它适用于通知:

首先,定义一个自定义 NSNotification.Name 和一个自定义 UIWindow 实现,如下所示:

let traitCollectionDidChangeNotification = NSNotification.Name("traitCollectionDidChange")

final class MyWindow: UIWindow {
    private var userInterfaceStyle = UITraitCollection.current.userInterfaceStyle

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        let currentUserInterfaceStyle = UITraitCollection.current.userInterfaceStyle
        if currentUserInterfaceStyle != userInterfaceStyle {
            userInterfaceStyle = currentUserInterfaceStyle
            NotificationCenter.default.post(name: traitCollectionDidChangeNotification,object: self)
        }
    }
}

如果你不想定义全局变量,你可以在实现中使用 Strings(虽然它可以外包给配置文件或其他任何东西):只需将 NotificationCenter.default.post(name: traitCollectionDidChangeNotification,object: self) 替换为 { {1}}。

然后写这个(如果你像我一样使用 NotificationCenter.default.post(name: NSNotification.Name("traitCollectionDidChange"),object: self),那么在 AppDelegate 中,如果 AppDelegate 然后在那里。我使用 SceneDelegate,所以这个例子适合,但它也适用于 AppDelegate):

SceneDelegate

从现在起,如果 @UIApplicationMain final class AppDelegate: UIResponder,UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { window = MyWindow(frame: UIScreen.main.bounds) // instantiate your root UIViewController. Of course you can do it anyhow,as you want,don't have to use Storyboards etc. window?.rootViewController = UIStoryboard(name: "xyz",bundle: nil).instantiateInitialViewController()! window?.makeKeyAndVisible() return true } // ... } 发生变化,您会收到通知。在自定义 userInterfaceStyle 实现中,您可以检查其他特征,而不必检查它是否真的发生了变化(MyWindow 属性)。

要获得这些通知,请将其写在任何地方(不仅仅是在 private userInterfaceStyleUIView 中):

UIViewController

例如,如果您想在 NotificationCenter.default.addObserver(forName: traitCollectionDidChangeNotification,object: nil,queue: .main) { _ in print("isDark: \(UITraitCollection.current.userInterfaceStyle == .dark)") // Do your things... } 中使用:

UIViewController

(我知道有一个关于移除通知观察者的争论 - 从 this explanation 起,我认为最好移除,但如果您不喜欢那样,只需忽略 {{1} }.)

我在真实设备(iPhone SE 2)上进行了测试,效果很好。如果您有任何问题,请随时提问!我希望它有帮助。 :)