简体   繁体   中英

SwiftUI: Animation Inside NavigationView

I am trying to create a simple animation in SwiftUI. It is basically a rectangle that changes its frame, while staying in the center of the parent view.

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("Text")
                ZStack {
                    Color.blue
                    SquareAnimation().frame(width: 200, height: 200, alignment: .center)
                }
                Text("Text")
            }
        }
    }
}

struct SquareAnimation: View {
    var currentRect = CGRect(x: 0, y: 0, width: 50, height: 50)
    var finalRect = CGRect(x: 0, y: 0, width: 100, height: 100)
    
    private let animation = Animation.easeInOut(duration: 1).repeatForever(autoreverses: true)
    
    @State var animate = false
    
    var body: some View {
        ZStack() {
            Color.clear
            Rectangle()
                .frame(width: animate ? finalRect.width: currentRect.width, height: animate ? finalRect.height: currentRect.height, alignment: .center)
                .animation(animation, value: animate)
                .onAppear() {
                    animate = true
                }
        }
        
    }
} 

The problem is, the black rectangle does not stay in the center if the NavigationView is used.
I have also used explicit animations with no avail. Why does the NavigationView affects the rectangle animation?

The onAppear is called too early when view frame is zero being in NavigationView, so animation is applied to change from zero to value.

Here is valid workaround. Tested with Xcode 12.4 / iOS 14.4

var body: some View {
    ZStack() {
        Color.clear
        Rectangle()
            .frame(width: animate ? finalRect.width: currentRect.width, height: animate ? finalRect.height: currentRect.height, alignment: .center)
            .animation(animation, value: animate)
            .onAppear {
                DispatchQueue.main.async {   
                   // << postpone till end of views construction !!
                    animate = true
                }
            }
    }
}

Note: almost any why question can be answered only by Apple... maybe it is a bug, maybe an implementation specifics.

This seems a general problem, in my experience. Here's a generalized solution that works for me – I know it's trivial, but I don't like necessary code, and thus want to minimize its repetition.

struct NavigationViewInitialLayoutWorkaround: ViewModifier {
    @Binding var canAnimate: Bool

    func body(content: Content) -> some View {
        content.onAppear {
            if !canAnimate {
                DispatchQueue.main.async {
                    canAnimate = true
                }
            }
        }
    }
}

Apply this modifier to any views wherever animations are affected by the initial layout problems of a NavigationView ancestor.

@State private var canAnimate: Bool = false

NavigationView {
    AllMyAnimatableViews()
        .animation(canAnimate ? .default : .none)
}
.modifier(NavigationViewInitialLayoutWorkaround(canAnimate: $canAnimate))

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