Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change SwiftUI alert color scheme?

Tags:

ios

swift

swiftui

I have implemented a functionality that changes the color scheme of the app. Everything works fine except the color scheme for the .alert() that has a TextField in it.

If is daylight and color scheme is changed to .dark, the .alert() color scheme is still .light includind the TextField.

That means that when you type something in the TextField, the foreground color is white, because the text color in dark mode changes to white, and you can't really see what you are typing.

Similar, if the system's color scheme is .dark and the app's color scheme is white, the .alert is in .dark mode and the text is black, because the text in the app is black.

Applying .background() or foregroundColor() to TextField or VStack doesn't have any effect.

Is there a way to control the color scheme for .alert()? Or do I have to look for an .alert() alternative?

Here is some code to reproduce the bahaviour. Thanks!

import SwiftUI

struct ContentView: View {
    @State private var showAlert = false
    @State private var addSomething: String = ""
    @State private var isSystemMode = false
    @State private var isDarkMode = false

    var body: some View {
        VStack {
            VStack {
                Toggle("Automatic (iOS Settings)", isOn: $isSystemMode)
                if !isSystemMode {
                    Toggle("Dark Mode", isOn: $isDarkMode)
                }
            }
            Button{
                self.showAlert = true
            } label: {
                Text("Add something")
                    .frame(maxWidth: .infinity)
            }
            .alert("Add something", isPresented: $showAlert, actions: {
                TextField("Something", text: $addSomething)
                Button("Done") {
                    // Some action
                }
                Button("Cancel", role: .cancel, action: {})
            }, message: {
                Text("Type in something")
            })
        }
        .padding()
        .preferredColorScheme(isSystemMode ? .none : isDarkMode ? .dark : .light)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
like image 784
s_diaconu Avatar asked Oct 28 '25 17:10

s_diaconu


1 Answers

I think the only way to truly make this work is by making your own custom alert. Here is an example of a custom alert, but you might have to modify it according to your needs:

struct CustomAlertView<Content: View>: View {

    @Environment(\.colorScheme) var colorScheme

    let title: String
    let description: String

    var cancelAction: (() -> Void)?
    var cancelActionTitle: String?

    var primaryAction: (() -> Void)?
    var primaryActionTitle: String?

    var customContent: Content?

    init(title: String,
         description: String,
         cancelAction: (() -> Void)? = nil,
         cancelActionTitle: String? = nil,
         primaryAction: (() -> Void)? = nil,
         primaryActionTitle: String? = nil,
         customContent: Content? = EmptyView()) {
        self.title = title
        self.description = description
        self.cancelAction = cancelAction
        self.cancelActionTitle = cancelActionTitle
        self.primaryAction = primaryAction
        self.primaryActionTitle = primaryActionTitle
        self.customContent = customContent
    }

    var body: some View {
        HStack {
            VStack(spacing: 0) {
                Text(title)
                    .font(.system(size: 16, weight: .semibold, design: .default))
                    .padding(.top)
                    .padding(.bottom, 8)

                Text(description)
                    .font(.system(size: 12, weight: .light, design: .default))
                    .multilineTextAlignment(.center)
                    .padding([.bottom, .trailing, .leading])

                customContent

                Divider()

                HStack {
                    if let cancelAction, let cancelActionTitle {
                        Button { cancelAction() } label: {
                            Text(cancelActionTitle)
                                .frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
                        }
                    }

                    if cancelActionTitle != nil && primaryActionTitle != nil {
                        Divider()
                    }

                    if let primaryAction, let primaryActionTitle {
                        Button { primaryAction() } label: {
                            Text("**\(primaryActionTitle)**")
                                .frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
                        }
                    }
                }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 50, alignment: .center)
            }
            .frame(minWidth: 0, maxWidth: 400, alignment: .center)
            .background(.ultraThickMaterial)
            .cornerRadius(10)
            .padding([.trailing, .leading], 50)
        }
        .zIndex(1)
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
        .background(
            colorScheme == .dark
            ? Color(red: 0, green: 0, blue: 0, opacity: 0.4)
            : Color(red: 1, green: 1, blue: 1, opacity: 0.4)
        )
    }
}

Usage:

struct ContentView: View {
    @State private var showAlert = false

    var body: some View {
        ZStack {
            Button("Show alert") {
                withAnimation {
                    showAlert.toggle()
                }
            }
            if showAlert {
                CustomAlertView(
                    title: "Alert title",
                    description: "Description here",
                    cancelAction: {
                        // Cancel action here
                        withAnimation {
                            showAlert.toggle()
                        }
                    },
                    cancelActionTitle: "Cancel",
                    primaryAction: {
                      // Primary action here
                        withAnimation {
                            showAlert.toggle()
                        }
                    },
                    primaryActionTitle: "Action",
                    customContent:
                        Text("Custom content here")
                        .padding([.trailing, .leading, .bottom])
                )
            }
        }
    }
}

If you want to include a TextField simply do:

customContent:
    TextField("Text here", text: $text)
    .textFieldStyle(.roundedBorder)
    .padding([.trailing, .leading, .bottom])

Preview of custom alert

like image 145
bjorn.lau Avatar answered Oct 31 '25 06:10

bjorn.lau