The standard way to check for a change between Dark and Light modes on iOS is with a view-level delegation function:
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()
}
}
This works well, but in an app with 100's of view controllers, adding this call to every controller is a pain in the butt and is messy. What I'd like to do is observe, via NotificationCenter, changes between light and dark on the AppDelegate level. That way I can make one global theme-changing function that applies to all views.
I tried the following in AppDelegate.swift but the daytimeDidChange never gets called when changing between dark/light:
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")
}
}
Any ideas on how to set up the Notification observer properly?
Let's see what we have. I write a full answer, so if you want, skip the first paragraph.
The standard solution - as you mentioned - would be to implement this method in every (of at least in some)
UIView
/UIViewController
:If that doesn't fit your needs, then here is another solution, which - as you want - works with notifications:
First, define a custom
NSNotification.Name
and a customUIWindow
implementation like this:You can work with
String
s in the implementation if you don't want to define a global variable (although it can be outsourced to a Config file or whatever): just replaceNotificationCenter.default.post(name: traitCollectionDidChangeNotification, object: self)
withNotificationCenter.default.post(name: NSNotification.Name("traitCollectionDidChange"), object: self)
.Then write this (if you use
AppDelegate
like me, then in theAppDelegate
, ifSceneDelegate
then there. I useAppDelegate
, so the example fits that, but it works withSceneDelegate
too):From now on you receive notifications if the
userInterfaceStyle
changed. In the customMyWindow
implementation you can check other traits, and you don't have to check that it really changed (theprivate userInterfaceStyle
property).To get these notifications, write it literally anywhere (not only in
UIView
orUIViewController
):For example if you want to use in a
UIViewController
:(I know that there is a debate about the removal of the notification observers - as of this explanation I think the best to remove, but if your taste doesn't like that, just ignore the
observer
.)I tested it on a real device (iPhone SE 2), it worked. If you have any question, feel free to ask! I hope it helps. :)