iosswiftios-darkmode

How to change theme style (User Interface Style) immediately in Swift in iOS 13


I'm using Swift 5.1 and Xcode 11.1 and I've currently finished implementing Dark Mode design.

App has setting where user can set display theme(Light, Dark, System Default) and currently it's working fine if app restarts after user selects theme(I save this bool data in UserDefaults and set UIAppearance at app startup in AppDelegate file)

Here's my code

if #available(iOS 13.0, *) {
   switch AppState.appThemeStyle {
   case "dark":
       window?.overrideUserInterfaceStyle = .dark
       break
   case "light":
       window?.overrideUserInterfaceStyle = .light
       break
   default:
       window?.overrideUserInterfaceStyle = .unspecified
   }
}

But I can see that many apps change display themes immediately after user sets theme style.

I think It's not good idea to restart app for only changing theme and theme should change immediately after user sets theme style.

I tried to do that by setting base viewcontroller and set user interface style on ViewWillAppear but Navigation bar & Tab bar appearance doesn't change.

Could anyone please tell me how to handle this? Thanks.


Solution

  • The SwiftUI way (No UIKit)

    3 simple steps:

    1. Make an storable theme
    2. Feed the app entrance with this value
    3. Make the change from anywhere you like
    enum SupportingThemes: String, CaseIterable { // ๐Ÿ‘ˆ Define your supporting schemes
        case light
        case dark
        case system
    
        var colorScheme: ColorScheme? { // ๐Ÿ‘ˆ A helper for ease of use
            switch self {
            case .light: .light
            case .dark: .dark
            case .system: nil 
            }
        }
    }
    
    @main
    struct SUPlaygroundApp: App {
        @AppStorage("Theme") var theme: SupportingThemes = .system // ๐Ÿ‘ˆ Depend on a single source of truth
    
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .preferredColorScheme(theme.colorScheme) // ๐Ÿ‘ˆ Feed the root view
            }
        }
    }
    

    You can now change it from anywhere using the AppStorage or directly with the UserDefaults:

    UserDefaults.standard.set("dark", forKey: "Theme")
    

    Original Answer

    This code is instant and there is no need to restart the app!

    You just need to call the code on user interaction with notification observer pattern or directly call the function.

    But the point is to access the visible window and overrideUserInterfaceStyle there. So for example you can set it from any visible view like:

    view.window?.overrideUserInterfaceStyle = .dark
    

    Deprecated but working method

    // Because you are using AppDelegate for this: (But it is going to be deprecated. read the following Note)
    (UIApplication.shared.delegate as! AppDelegate).myFunctionThatChangesTheUserInterfaceStyle()
    

    Also note that since iOS 13, the key window is not a part of appDelegate anymore and you should access it from sceneDelegate