View presented as sheet are properly updated to correct color scheme

I’m facing an issue and I’d like to know if anyone has already run into this.

I have a ContentView that presents a SettingsView as a sheet. SettingsView applies a change to the app’s colorScheme. ContentView reacts correctly to the change, and SettingsView does too (so far, so good).

What’s strange is that when I set nil on the preferredColorScheme modifier (which, according to the docs, corresponds to the system color scheme), ContentView correctly picks up the change and refreshes, while SettingsView does pick up the change but doesn’t refresh. (In the video you can clearly see that when I switch from Dark to System, the parent view refreshes properly but not the presented sheet.)

I’ve tried everything—switching to UIKit, changing the sheet’s ID… nothing works

Another strange thing: if I present SettingsView through a NavigationLink, everything works normally…

Here is a sample code to reproduce:

import SwiftUI

enum AppTheme: Int {
    case system = 0
    case dark = 1
    case light = 2
    
    var colorScheme: ColorScheme? {
        switch self {
        case .system: return nil
        case .light:  return .light
        case .dark:   return .dark
        }
    }
}


struct SettingsView: View {
    
    @AppStorage("theme") var appTheme: AppTheme = .system
    
    var body: some View {
        VStack(spacing: 8) {
            Button {
                select(theme: .system)
            } label: {
                Text("Systeme")
            }
            
            Button {
                select(theme: .dark)
            } label: {
                Text("Dark")
            }
            
            Button {
                select(theme: .light)
            } label: {
                Text("Light")
            }

        }
        .preferredColorScheme(appTheme.colorScheme)
    }
    
    func select(theme: AppTheme) {
        appTheme = theme
    }
}

struct ContentView: View {
    
    @AppStorage("theme") var appTheme: AppTheme = .system
    @State var isPresented = false
    var body: some View {
        NavigationStack {
            VStack {
                Button {
                    isPresented = true
                } label: {
                    Text("Present settings")
                }
//                NavigationLink("Present settings") {
//                    SettingsView()
//                }
            }
            .preferredColorScheme(appTheme.colorScheme)
            .sheet(isPresented: $isPresented) {
                SettingsView()
            }
        }
    }
}

#Preview {
    ContentView()
}

Answered by dim971 in 856656022

Someone (Andy) showed me a "workaround" (i guess?) by using the @Environment(\.colorScheme) to update the proper color scheme for sheets (see the updated ContentView)

BTW I still think this is a SwiftUI issue. Sheets should behave the same way other views are presented.

Here is a working version of the sample:

import SwiftUI

enum AppTheme: Int {
    case system = 0
    case dark = 1
    case light = 2
    
    var colorScheme: ColorScheme? {
        switch self {
        case .system: return nil
        case .light:  return .light
        case .dark:   return .dark
        }
    }
}


struct SettingsView: View {
    
    @AppStorage("theme") var appTheme: AppTheme = .system
    
    var body: some View {
        VStack(spacing: 8) {
            Button {
                select(theme: .system)
            } label: {
                Text("System")
            }
            
            Button {
                select(theme: .dark)
            } label: {
                Text("Dark")
            }
            
            Button {
                select(theme: .light)
            } label: {
                Text("Light")
            }

        }
    }
    
    func select(theme: AppTheme) {
        appTheme = theme
    }
}

struct ContentView: View {
    
    @AppStorage("theme") var appTheme: AppTheme = .system
    @State var isPresented = false
    @Environment(\.colorScheme) var systemColorScheme
    
    private var effectiveColorScheme: ColorScheme {
        // For sheets, we can't pass nil to preferredColorScheme, so we resolve the actual system scheme
        appTheme.colorScheme ?? systemColorScheme
    }
    
    var body: some View {
        NavigationStack {
            VStack {
                Button {
                    isPresented = true
                } label: {
                    Text("Present settings")
                }
            }
            .preferredColorScheme(appTheme.colorScheme)
            .sheet(isPresented: $isPresented) {
                SettingsView()
                    .preferredColorScheme(effectiveColorScheme)
            }
        }
    }
}

#Preview {
    ContentView()
}

BTW there is a missing word in the title. The correct title should be: View presented as sheet are NOT properly updated to correct color scheme

Accepted Answer

Someone (Andy) showed me a "workaround" (i guess?) by using the @Environment(\.colorScheme) to update the proper color scheme for sheets (see the updated ContentView)

BTW I still think this is a SwiftUI issue. Sheets should behave the same way other views are presented.

Here is a working version of the sample:

import SwiftUI

enum AppTheme: Int {
    case system = 0
    case dark = 1
    case light = 2
    
    var colorScheme: ColorScheme? {
        switch self {
        case .system: return nil
        case .light:  return .light
        case .dark:   return .dark
        }
    }
}


struct SettingsView: View {
    
    @AppStorage("theme") var appTheme: AppTheme = .system
    
    var body: some View {
        VStack(spacing: 8) {
            Button {
                select(theme: .system)
            } label: {
                Text("System")
            }
            
            Button {
                select(theme: .dark)
            } label: {
                Text("Dark")
            }
            
            Button {
                select(theme: .light)
            } label: {
                Text("Light")
            }

        }
    }
    
    func select(theme: AppTheme) {
        appTheme = theme
    }
}

struct ContentView: View {
    
    @AppStorage("theme") var appTheme: AppTheme = .system
    @State var isPresented = false
    @Environment(\.colorScheme) var systemColorScheme
    
    private var effectiveColorScheme: ColorScheme {
        // For sheets, we can't pass nil to preferredColorScheme, so we resolve the actual system scheme
        appTheme.colorScheme ?? systemColorScheme
    }
    
    var body: some View {
        NavigationStack {
            VStack {
                Button {
                    isPresented = true
                } label: {
                    Text("Present settings")
                }
            }
            .preferredColorScheme(appTheme.colorScheme)
            .sheet(isPresented: $isPresented) {
                SettingsView()
                    .preferredColorScheme(effectiveColorScheme)
            }
        }
    }
}

#Preview {
    ContentView()
}
View presented as sheet are properly updated to correct color scheme
 
 
Q