Sheet will change to dark theme from toggle but won't change back to light mode

I have my ContentView which has a Sheet that will appear when a button is pressed.

struct ContentView: View {

    @EnvironmentObject private var settings: SettingsHandler
    @State private var settingsView: Bool = false

    var body: some View {
        NavigationStack {
            Button(action: {
                settingsView.toggle()
            }, label: {
                Image(systemName: "gearshape.fill")
            })
        }
        .preferredColorScheme(settings.darkTheme ? .dark : nil)
        .sheet(isPresented: $settingsView, content: {
            SettingsView()
        })
    }
}

Let's say the app is in light mode based on the phones theme settings. You open the SettingsView and select the toggle that will switch to dark mode. Everything changes to dark mode, including the SettingsView sheet. Then you select the same toggle to switch back and ContentView in the background changes to light theme but the sheet doesn't until you close and reopen it. I would think it would change back considering it changed to dark mode without needing to be closed.

I tried attaching an ID to the SettingsView and having it refresh when settings.darkTheme is changed, however, it doesn't seem to be doing anything. I also added the .preferredColorScheme() modifier into the SettingsView, but it did nothing. I also replaced the nil to .light, and the same issue occurred. Settings is an EnvironmentObject that I created to manage all the Settings I have.

At the moment, I'm thinking I can have the sheet just close and reopen, however, I would like for it to update properly. Any ideas?

I've never figured out how to make forced color themes work in pure SwiftUI but this is what I did for one of my apps that worked fine for me. ( I didn't try it specifically from a sheet but I'd be very confused if it didn't work

Note: This is for the whole app, not only for one view.

// Create an enum case for every theme
enum ColorTheme: String, CaseIterable {
    case light
    case dark
    case system
}

@main
struct ExampleApp: App {
     // Save user's choice in preferences (userdefaults)
    @AppStorage("colorTheme") private var colorTheme: ColorTheme = .system
    
    var body: some Scene {
        WindowGroup {
            SplashScreen()
                .onAppear {
                    overrideColorTheme()
                }
                .onChange(of: colorTheme) { _ in
                    overrideColorTheme()
                }
        }
    }

    func overrideColorTheme() {
        var userInterfaceStyle: UIUserInterfaceStyle
        
        switch colorTheme {
            case .light:
                userInterfaceStyle = .light
            case .dark:
                userInterfaceStyle = .dark
            case .system:
                userInterfaceStyle = .unspecified
        }
        
        UIApplication.shared.windows.first?.overrideUserInterfaceStyle = userInterfaceStyle
    }
}

Now from anywhere in your app you can update the value stored in UserDefaults and it will update the color theme used in your whole app.

Like so:

struct ExampleView: View {
    @AppStorage("colorTheme") private var colorTheme: ColorTheme = .system
    
    var body: some View {
        VStack {
            Button("Set to Light") { colorTheme = .light }
            Button("Set to Dark") { colorTheme = .dark }
            Button("Set to System") { colorTheme = .system }
        }
    }
}

Just checked and saw that since iOS 15.0 UIApplication.shared.windows is deprecated so you could use an extension like this for 15 and above

extension UIApplication {
    
    func setColorTheme(to theme: UIUserInterfaceStyle) {
        let keyWindow =  self.connectedScenes
        // Keep only active scenes, onscreen and visible to the user
            .filter { $0.activationState == .foregroundActive }
        // Keep only the first `UIWindowScene`
            .first(where: { $0 is UIWindowScene })
        // Get its associated windows
            .flatMap({ $0 as? UIWindowScene })?.windows
        // Finally, keep only the key window
            .first(where: \.isKeyWindow)
        
        keyWindow?.overrideUserInterfaceStyle = theme
    }
}

And use it like so in the previous mentioned overrideColorTheme() function:

UIApplication.shared.setColorTheme(to: userInterfaceStyle)

The issue is setting .preferredColorScheme on the sheet. A nil value (for system appearance) does nothing.

A full working solution without using UIKit can be found here:

https://gist.github.com/chockenberry/a2a23a12604e333b1c2a8b71e7a24155

Apple folks: FB15105356

Sheet will change to dark theme from toggle but won't change back to light mode
 
 
Q