5

I don't know if this is supported yet with SwiftUI, but I'm trying to animate the transition from Light/Dark mode app-wide when the button is a. I have a navigation bar on the bottom of the screen with a button that switches from a sun to moon icon when tapped.

The switch from light mode to dark mode and vice-versa does work, but is immediate and looks like someone just flipped the lights off. The animation use in the following code only changes the image of my button with animation.

Here is what I have:

NavigationBarView.swift

struct NavigationBarView: View {

@AppStorage("isDarkMode") private var isDarkMode = false
...

var body: some View {
    ZStack {
        ...
            Button(action: {
                withAnimation(.easeInOut) {
                    isDarkMode.toggle()
                }
            }, label: {
                if isDarkMode {
                    Image.sunButton
                        .resizable()
                        .imageScale(.large)
                        .frame(width: 24, height: 24)
                        .foregroundColor(Color.darkGray)
                } else {
                    Image.moonButton
                        .resizable()
                        .imageScale(.large)
                        .frame(width: 24, height: 24)
                        .foregroundColor(Color.darkGray)
                }
                
            })
            
        ...
        
        }
    }
    ...
}

}

LucidityApp.swift

@main
struct LucidityApp: App {

@Environment(\.colorScheme) var colorScheme
@AppStorage("isDarkMode") private var isDarkMode = false

var body: some Scene {
    WindowGroup {
        Dashboard()
            .preferredColorScheme(isDarkMode ? .dark : .light)
            .background(colorScheme == .dark ? Color.darkGray : Color.white)
    }
}

}

Thank you in advance for the help, and I am new to SwiftUI so if you have any best practice(s) tips, I will gladly listen to them!

Shane

EDIT: I found the solution, I wrapped the WindowGroup in the main app SwiftUI file in a VStack, and applied .animation to the VStack with .animation(.spring(), value: isDarkMode) and that made the transition from light to dark mode the way I wanted it.

  • How you want this transition happen? fade or offset or scale or rotation, there are so many options, you did not tell how? – ios coder May 15 '22 at 23:29
  • @swiftPunk, I want the colors to fade from dark to light and light to dark – Shane Curtis May 16 '22 at 02:34
  • This looks like an animation bug with `@AppStorage`. Does it work if you swap to `@State`? I'm not suggesting you get rid of the AppStorage persistence, just asking you to test it. – joshuakcockrell Oct 28 '22 at 17:40

2 Answers2

1

To get the animation to work you need to add a transition, Transitions control how the insertion and removal of a view takes place.

    Button(action: {
        withAnimation(.easeInOut) {
            isDarkMode.toggle()
        }
    }, label: {
        if isDarkMode {
            Image.sunButton
                .resizable()
                .imageScale(.large)
                .frame(width: 24, height: 24)
                .foregroundColor(Color.darkGray)
                .transition(.asymmetric(insertion: .opacity, removal: .opacity))
        }
        else {
            Image.moonButton
                .resizable()
                .imageScale(.large)
                .frame(width: 24, height: 24)
                .foregroundColor(Color.darkGray)
                .transition(.asymmetric(insertion: .opacity, removal: .opacity))
        }
        
    })

How the transition behaves can also be changed depending on your needs please see apple documentation

yawnobleix
  • 1,204
  • 10
  • 21
  • Hi, I think you misunderstood my question. The image for the toggle button is animated how I would like already, the issue is the background and all of the colors of the view changing to dark mode is not animated – Shane Curtis May 16 '22 at 19:28
0

You’re doing the right thing to animate it in SwiftUI, so it must not work. Maybe file a bug with Apple?

Luckily you can animate light/dark mode switch if you use UIKit instead. You’ll have to get ahold of your UIWindow(s) somehow and then animate the property overrideUserInterfaceStyle per this answer:

if let window = UIApplication.shared.keyWindow {
    UIView.transition (with: window, duration: 0.3, options: .transitionCrossDissolve, animations: {
        window.overrideUserInterfaceStyle = .dark //.light or .unspecified
    }, completion: nil)
}

Just drop that code in your Button’s action and it should work.

Adam
  • 4,405
  • 16
  • 23
  • Thank you, probably will submit a ticket. Your code does work perfectly as expected, although only in the canvas preview. Not on a physical device. heh, thanks anyways – Shane Curtis May 16 '22 at 03:05
  • Ah sorry, I just realized that’s using the deprecated keyWindow property. Try looping over UIApplication.shared.connectedScenes and getting the active window(s) from there. – Adam May 20 '22 at 21:51