3

I found a solution to manually set light/dark mode in a swiftui app in another thread found here https://stackoverflow.com/a/58476468/11698443 it mostly works, but there are two problems.

  1. The users choice is not permanently saved.

  2. I would like the default choice to be dark mode, so the app will initially show up in dark mode whether the user has the system set to light or dark mode.

Right now, this implementation is a little buggy because if the user opens the app in light mode and hits the toggle switch. The first time they hit the switch will do nothing. They will have to hit the switch two more times to fire the didSet to get the app into dark mode and even then, the choice won't be saved.

A few other threads ask about dark mode implementation, but most deal with UIKit and the thread I linked to above was the only solution I could get to mostly work in swiftui. Is it possible to modify that solution to address the two issues that I brought up?

Asperi
  • 228,894
  • 20
  • 464
  • 690
Ryan
  • 630
  • 8
  • 20
  • dude what.... just let the OS handle it – jbiser361 Apr 10 '20 at 15:07
  • People set the light or dark mode on the system and expect it to be respected by your app. Your app should have nothing to do with setting light and dark mode. – HalR Apr 10 '20 at 15:19
  • 2
    What if you have an app that is only appropriate in light mode? Visually impaired reasons, or you are trying to show graphics that don't show up in dark mode ... – addzo Jan 15 '21 at 14:37

2 Answers2

14

PStamatiou.

This worked for me when not using a scene delegate and only having the content view:

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.colorScheme, .light)
        }
    }
}
addzo
  • 845
  • 3
  • 13
  • 37
11

Here is possible approach (scratchy, you can find here on SO property wrapper for defaults and use it for better styling, but the idea to achieve your goal is the same)

Tested with Xcode 11.4 / iOS 13.4

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    private(set) static var shared: SceneDelegate?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        Self.shared = self

        let contentView = ContentView()

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)

            // restore from defaults initial or previously stored style
            let style = UserDefaults.standard.integer(forKey: "LastStyle")
            window.overrideUserInterfaceStyle = (style == 0 ? .dark : UIUserInterfaceStyle(rawValue: style)!)

            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }

   ...
}


struct ContentView: View {
    var body: some View {
         // manipulate with style directly in defaults
         Toggle(isOn: Binding<Bool>(
            get: { UserDefaults.standard.integer(forKey: "LastStyle") !=
                        UIUserInterfaceStyle.light.rawValue },
            set: {
                SceneDelegate.shared?.window!.overrideUserInterfaceStyle = $0 ? .dark : .light
                UserDefaults.standard.setValue($0 ? UIUserInterfaceStyle.dark.rawValue : UIUserInterfaceStyle.light.rawValue, forKey: "LastStyle")
            }
         )) {
             Text("is Dark")
        }
    }
}

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • 5
    How does this work with iOS 14 not having SceneDelegate or window (if you have an iOS14+ only app)? Is there a way to apply it to WindowGroup{}? – PStamatiou Jul 15 '20 at 14:48
  • I have the same question. Can't find a way to be compliant with the "new" Apple way and have light mode when appropriate. – addzo Jan 15 '21 at 14:35
  • Thank you it working fine with me @PStamatiou @addzo even when you start your app as SwiftUI App life circle , you can create new project as UIkit delegate then remove `ProjectApp` file (the file have @main) then replace it with `AppDelegate` and `SceneDelegate` then go to `Info.plist` and compare between new project with AppDelagte and your current project , you will find some value deffrent change it and you good to go ! – Basel Feb 12 '21 at 16:00