简体   繁体   中英

SwiftUI animation problem with a binding to a StateObject inside a NavigationView

I have an interesting situation in regards to animations in SwiftUI. In its simplified form, I have a view that shows a rectangle which, when tapped, should toggle a Bool binding and represent the change with an animated transition of color. But it seems like the animation doesn't happen when the view is inside a NavigationView and the binding is coming from a StateObject instead of simple local state. I can't explain why that would be the case, and would appreciate any thoughts.

Below is the code that shows a simplified case that reproduces the issue. The app code isn't particularly interesting; it's the default code that creates a WindowGroup and shows an instance of ContentView in it.

import SwiftUI

class AppState: ObservableObject {
    @Published var isRed = false
}

struct RectView: View {
    @Binding var isRed: Bool

    var body: some View {
        Rectangle()
            .fill(isRed ? Color.red : Color.gray)
            .frame(width: 75, height: 75, alignment: .center)
            .onTapGesture {
                withAnimation(.easeInOut(duration: 1)) {
                    isRed.toggle()
                }
            }
    }
}

struct ContentView: View {
    @StateObject var appState = AppState()
    @State private var childViewIsRed = false

    var body: some View {
        VStack {
            NavigationView {
                List {
                    NavigationLink("Link with binding to state object", destination: RectView(isRed: $appState.isRed))
                    NavigationLink("Link with binding to state variable", destination: RectView(isRed: $childViewIsRed))
                }
            }
            .frame(height: 300)

            RectView(isRed: $appState.isRed)
            RectView(isRed: $childViewIsRed)
        }
    }
}

The gif/video below is me demonstrating four things, tapping on these views from bottom to top:

  • First I tap on the very bottom rectangle - the one with a binding to a @State property. It toggles with animation as expected. I tap again to leave it gray.
  • Then I tap the second rectangle from the bottom - one with a binding to a @Published property in the @StateObject . All is well.
  • Next, I tap on the NavigationLink that leads to a rectangle that is bound to the local @State property. When I tap on the rectangle the transition is animated fine. The very bottom rectangle also animates, which makes sense since they are bound to the same property.
  • Finally I tap on the top NavigationLink , which leads to a rectangle bound to the @Published property in the @StateObject . When I tap on this rectangle though, there is no animation. The rectangle snaps to red. The rectangle below it (which is bound to the same property) animates fine, proving that the property is indeed toggled. But there is no animation inside the NavigationView . Why? What am I missing?

I've searched for existing questions. I'm aware that there are some around NavigationView ( like this ) but that doesn't explain why one type of binding would work fine inside a NavigationView and another wouldn't. Similarly, there are ones around animating changes to @ObservedObject s ( like this , would a @StateObject be similar?) but I don't follow why animating changes to such a binding would work fine outside of a NavigatonView and not inside one.

In case it's relevant I'm using Xcode 13.2.1, running on macOS 12.2.1, which is in turn running on a 16-inch M1 Max MacBook Pro. The simulator shown in the gif/video is an iPhone 11 Pro Max. I get the same results if I deploy this tiny test app to a physical iPhone 7 running iOS 15.3.1.

在此处输入图像描述

To be honest with you, I am not sure why, but utilizing the animation modifier on the RectView allows the animation to occur without any issues.

struct RectView: View {
@Binding var isRed: Bool

    var body: some View {
        Rectangle()
            .fill(isRed ? Color.red : Color.gray)
            .frame(width: 75, height: 75, alignment: .center)
            .animation(.easeOut(duration: 1), value: isRed)
            .onTapGesture { isRed.toggle() }
    }
}

screen recording example

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM