问题描述
在 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)
}
}
}
如果你不想定义全局变量,你可以在实现中使用 String
s(虽然它可以外包给配置文件或其他任何东西):只需将 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 userInterfaceStyle
或 UIView
中):
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)上进行了测试,效果很好。如果您有任何问题,请随时提问!我希望它有帮助。 :)