Search code examples
ios13uitraitcollectionios-darkmode

Darkmode and multiwindows / scenes


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.


Solution

  • 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
                     }
                 }
             }
         }