drekka
drekka

Reputation: 21883

Slider transition animation bug

I have this strange animation bug I've not been able to figure out. When transitioning on a view that contains a slider, the slider appears to animate independently of the rest of the view.

enter image description here

For reference, here is the code that animates the two views:

@main
struct TransitionAnimationBug: App {

    @State var displaySettings = false

    var body: some Scene {

        WindowGroup {
            Group {
                if displaySettings {
                    SliderView(displaySettings: $displaySettings)
                        .transition(AnyTransition.asymmetric(
                            insertion: .move(edge: .trailing),
                            removal: .move(edge: .leading)
                        ))
                } else {
                    MainView(displaySettings: $displaySettings)
                        .transition(AnyTransition.asymmetric(
                            insertion: .move(edge: .leading),
                            removal: .move(edge: .trailing)
                        ))
                }
            }
            .animation(.easeInOut, value: displaySettings) // Shows bug
//            .animation(.spring(), value: displaySettings) // Works
        }
    }
}

And here is the code from the settings screen that displays the slider:

struct SliderView: View {

    var displaySettings: Binding<Bool>

    @State var sliderValue: Double = 4.0

    init(displaySettings: Binding<Bool>) {
        self.displaySettings = displaySettings
    }

    var body: some View {
        VStack(spacing: 30) {
            Text("Hello, world!")
            Slider(value: $sliderValue, in: 0 ... 4, step: 1)
                .border(.red)
                .padding(20)
            Button("Close") { displaySettings.wrappedValue.toggle() }
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.black.opacity(0.2))
    }
}

And for completeness the main view:

struct MainView: View {

    var displaySettings: Binding<Bool>

    init(displaySettings: Binding<Bool>) {
        self.displaySettings = displaySettings
    }

    var body: some View {
        VStack(spacing: 30) {
            Text("Hello, world!")
                .padding()
            Button("Show transition bug") {
                displaySettings.wrappedValue.toggle()
            }
            Text("The slider will animate incorrectly")
                .padding()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.white)
    }
}

As you can see this is a minimal app I built to show the bug (which I've submitted to Apple via their Feedback assistant), but I was wondering if anyone here as encountered it and found a solution.

Through experimenting I found that using a .spring() animation instead of .slideInOut correctly animates the slider, but any other animation fails.

Any thoughts?

Upvotes: 1

Views: 297

Answers (2)

drekka
drekka

Reputation: 21883

Thanks Asperi, that worked. Do you know why? On first glance there's no obvious reason why it would work other than it being something to do with the fact that the body returns a Scene as opposed to a View.

The changed code looks like this:

@main
struct TransitionAnimationBug: App {

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {

    @State var displaySettings = false

    var body: some View {

        Group {
            if displaySettings {
                SliderView(displaySettings: $displaySettings)
                    .transition(AnyTransition.asymmetric(
                        insertion: .move(edge: .trailing),
                        removal: .move(edge: .leading)
                    ))
            } else {
                MainView(displaySettings: $displaySettings)
                    .transition(AnyTransition.asymmetric(
                        insertion: .move(edge: .leading),
                        removal: .move(edge: .trailing)
                    ))
            }
        }
        .animation(.easeInOut, value: displaySettings)
    }
}

Upvotes: 0

Asperi
Asperi

Reputation: 257779

This is effect of changing root view of window. The fix is to move the content of WindowGroup with all related states into separated view (to make root of window persistent), so it looks like

WindowGroup {
  ContentView()      // << everything else move here !!
}

Tested with Xcode 13.3 / iOS 15.4

Upvotes: 1

Related Questions