i'm trying to implement ios13 darkmode within multi scene application.
Unfortunately when i dismiss a scene dragging it over the screen edge the method traitCollectionDidChange is called several times with always different values, causing my UI to flicker between dark and light mode.
What's wrong?
Here is my implementation
func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
print("THEME instance: \(self)")
let currentTraitCollection = self.traitCollection
var hasUserInterfaceStyleChanged = false
hasUserInterfaceStyleChanged = previousTraitCollection.hasDifferentColorAppearanceCompared(to: currentTraitCollection)
print("THEME hasUserInterfaceStyleChanged = \(hasUserInterfaceStyleChanged ? "YES" : "NO")")
if hasUserInterfaceStyleChanged {
let userInterfaceStyle = currentTraitCollection.userInterfaceStyle // Either .unspecified, .light, or .dark
switch userInterfaceStyle {
case .unspecified:
print("THEME UIUserInterfaceStyleUnspecified")
case .light:
print("THEME UIUserInterfaceStyleLight")
case .dark:
print("THEME UIUserInterfaceStyleDark")
}
} else {
print("THEME NOT CHANGED")
}
}
Here is the logged statements in console
When new scene comes in...
THEME instance: <MainControllerViewController: 0x117e55910>
THEME hasUserInterfaceStyleChanged = YES
THEME UIUserInterfaceStyleLight
When added scene goes away...
THEME instance: <MainControllerViewController: 0x117e55910>
THEME hasUserInterfaceStyleChanged = YES
THEME UIUserInterfaceStyleDark
THEME instance: <MainControllerViewController: 0x117e55910>
THEME hasUserInterfaceStyleChanged = YES
THEME UIUserInterfaceStyleLight
THEME instance: <MainControllerViewController: 0x117e55910>
THEME hasUserInterfaceStyleChanged = NO
THEME NOT CHANGED
THEME instance: <MainControllerViewController: 0x117e55910>
THEME hasUserInterfaceStyleChanged = YES
THEME UIUserInterfaceStyleDark
THEME instance: <MainControllerViewController: 0x117e55910>
THEME hasUserInterfaceStyleChanged = YES
THEME UIUserInterfaceStyleLight
in meantime i have no changed to dark mode (always light)...so i expect just THEME NOT CHANGED.
I was struggling with the same issue and the solution I worked out is in the SceneDelegate
.
A UIScene
has multiple states:
.foregroundActive
.foregroundInactive
.background
.unattached
When you are resizing windows in slide over, or in this case, removing one from slide over, traitCollectionDidChange
gets called for each of them. This means that you are updating the userInterfaceStyle
for scenes in the .background
, .foregroundInactive
, and .unattached
states. This is what's causing the flickering.
The solution is to not use traitCollectionDidChange
, but to use a delegate method in SceneDelegate
called windowScene(_:didUpdate:interfaceOrientation:traitCollection:)
.
Per Apple's docs this method:
Notifies you when the size, orientation, or traits of a scene change.
The added benefit of this is that we can check the .activationState
of the scene before updating the userInterfaceStyle
.
func windowScene(_ windowScene: UIWindowScene, didUpdate previousCoordinateSpace: UICoordinateSpace, interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation, traitCollection previousTraitCollection: UITraitCollection) {
let currentTraitCollection = windowScene.traitCollection
if windowScene.activationState == .foregroundActive {
if currentTraitCollection.userInterfaceStyle != previousTraitCollection.userInterfaceStyle {
if currentTraitCollection.userInterfaceStyle == .light {
//update to light theme
} else {
//update to dark theme
}
}
}
}