4

So, I am supporting three themes in my app, each with different tintColors. I'm using @EnvironmetObject to track changes. However, I can't use it on SceneDelegate.swift file, because the app crashes. Moreover, accentColor is not an option, as it doesn't change alert tintColor. How can I do it?

Here's some code:

SceneDelegate.swift file

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

@EnvironmentObject var userData: UserData

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Create the SwiftUI view that provides the window contents.
    let contentView = TasksView()

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: contentView.environmentObject(UserData()))
        self.window = window
        window.makeKeyAndVisible()

        window.tintColor = userData.selectedTheme.tintColor
    }
}

This approach will crash when the app starts, because it can't finde an @EnvironmentObject in its ancestor.

ContentView.swift file

struct ContentView: View {

    @EnvironmentObject var userData: UserData

    var body: some View {
        NavigationView{
            List(userData.tasks) { task in
                TaskRow(taskTitle: task.title, taskDetail: task.detail)
            }
            .navigationBarTitle(Text("Tasks"), displayMode: .automatic)

            .navigationBarItems(
                leading: NavigationLink(destination: SettingsView(), label: {
                    Image(systemName: "gear").imageScale(.large)
                }),
                trailing: NavigationLink(destination: AddTaskView(), label: {
                    Image(systemName: "plus").imageScale(.large)
                })
            )
        }.navigationViewStyle(StackNavigationViewStyle())
         .accentColor(userData.selectedTheme.accentColor)
    }
}

This approach won't work for me either because it doesn't change the tintColor of alerts, for example.

Images

This is what I get if I use accentColor

This is what I get if I use accentColor


This is what I want to achieve

This is what I want to achieve

Alejo Flores
  • 83
  • 1
  • 6
  • 1
    Can you provide a bit more context? Like show us the code you are trying to put in `SceneDelegate`, and what do you mean by it does not change the alert tint? – Glenn Posadas Feb 24 '20 at 03:27
  • Hey @Glenn I just added what you asked. Any chance you know how to do it now? – Alejo Flores Feb 24 '20 at 14:52
  • I've tried everything. I reckon you can't do anything with it. Setting `UIButton.appearance()` won't work too. `Alert.Button` is a struct, isn't meant to be subclassed, it's not a `View` either that you can change its color properties. – Glenn Posadas Feb 24 '20 at 16:49
  • `UIView.appearance().tintColor = .green` will make the SwiftUI Alert buttons green. – Mike Taverne Apr 25 '20 at 03:40

3 Answers3

4

In your Assets.xcassets file should be an entry for AccentColor. Click on this entry and select the Universal rectangle. Afterwards you can set any color you want in the attribute inspector on the right hand side.

This will set the color for all your views and applies even to modal views, which behave kind of weird if you try to override them manually.

Xcode screenshot

tomaculum
  • 547
  • 4
  • 15
  • This should bee the accepted answer, at least for SwiftUI 2.0 and SwiftUI 3.0. Thanks! – Juan Manuel Gentili Oct 23 '21 at 02:20
  • It works really well with modals and stuff but if I do it this way, how can a user customize what color they want? – user2619824 Nov 24 '21 at 23:07
  • One way could be to save the color value inside an e.g. `@Published` variable and pass this value as `.accentColor(yourColorVar)` to your content view. I do not know if there are any side effects or if this is the preferred way to do this but its the first thing which came to my mind. – tomaculum Dec 15 '21 at 09:50
1

Update: posted this demo on GitHub - DemoWindowTint

The below demo is created on setting window's tintColor (which is inherited by all subviews) using the approach provided in How to access own window within SwiftUI view?.

In demo I used NavigationView with couple of NavigationLinks and Button showing Alert.

demo

Tested with following

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?


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

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

            let contentView = ContentView()
                .environment(\.hostingWindow, { [weak window] in
                    return window })

            window.rootViewController = UIHostingController(rootView: contentView)

            self.window = window
    ...

struct ContentView: View {
    @Environment(\.hostingWindow) var hostingWindow
   ... // body can be any

    .onAppear {
            // can be loaded from UserDefaults here, and later changed on any action
            self.hostingWindow()?.tintColor = UIColor.red
    }
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Hey @Asperi . I can't make it work! Would you please post your SceneDelegate file too please? – Alejo Flores Feb 24 '20 at 19:12
  • @AlejoFlores, updated. And you need to copy everything related `hostingWindow` from that provided link. – Asperi Feb 24 '20 at 19:16
  • Hey @Asperi sorry to be such a pain in the neck but I can't figure out why this is not working. I have added the struct and the extension and I've done the exact same thing on SceneDelegate. The only thing it may be causing an issue is the fact that I'm using `@EnvironmentObject` too. Do you think that is the problem? – Alejo Flores Feb 24 '20 at 19:54
  • No, in my real project I also use environment object. – Asperi Feb 24 '20 at 19:57
  • @AlejoFlores, what exactly does not work? Is hostingWindow not-nil in onAppear? – Asperi Feb 24 '20 at 20:03
  • Hey @Asperi . I figured what the problem is, but I have no idea how to fix it. What's happening is that `hostingWindow()` keeps returning `nil` instead of `UIWindow`. Any idea why? – Alejo Flores Feb 24 '20 at 20:04
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/208445/discussion-between-asperi-and-alejo-flores). – Asperi Feb 24 '20 at 20:09
1

You can achieve it by calling

UIView.appearance(whenContainedInInstancesOf: [UIAlertController.self]).tintColor = .red

in willConnectTo method of SceneDelegate

rgreso
  • 496
  • 1
  • 7
  • 19