SwiftUI:NavigationView 中的显式动画损坏?

问题描述 投票:0回答:3

当我在 NavigationView 中放置显式动画时,作为不良副作用,它会为 NavigationView 内容的初始布局设置动画。对于无限动画来说,情况尤其糟糕。有没有办法消除这个副作用?

示例:下图应该是全屏蓝色背景上的动画红色加载程序。相反,我得到了缩放蓝色背景的无限循环:

import SwiftUI

struct EscapingAnimationTest: View {
    var body: some View {
        NavigationView {
            VStack {
                Spacer()
                EscapingAnimationTest_Inner()
                Spacer()
            }
            .backgroundFill(Color.blue)
        }
    }
}

struct EscapingAnimationTest_Inner: View {
    @State var degrees: CGFloat = 0
    
    var body: some View {
        Circle()
            .trim(from: 0.0, to: 0.3)
            .stroke(Color.red, lineWidth: 5)
            .rotationEffect(Angle(degrees: degrees))
            .onAppear() {
                withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                    degrees = 360
                }
            }
    }
}

struct EscapingAnimationTest_Previews: PreviewProvider {
    static var previews: some View {
        EscapingAnimationTest()
    }
}
animation swiftui
3个回答
48
投票

这是固定部分(我的另一个带有解释的答案在这里)。

使用 Xcode 12 / iOS 14 进行测试。

struct EscapingAnimationTest_Inner: View {
    @State var degrees: CGFloat = 0
    
    var body: some View {
        Circle()
            .trim(from: 0.0, to: 0.3)
            .stroke(Color.red, lineWidth: 5)
            .rotationEffect(Angle(degrees: Double(degrees)))
            .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false), value: degrees)
            .onAppear() {
                DispatchQueue.main.async {   // << here !!
                    degrees = 360
                }
            }
    }
}

更新:同样将使用

withAnimation

.onAppear() {
    DispatchQueue.main.async {
        withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
           degrees = 360
        }
    }

}

0
投票

DispatchQueue.main.async
块之外使用
withAnimation
对我有用,但这段代码看起来不太干净。 我找到了另一个(在我看来更干净的)解决方案:

  • 在体外创建一个
    isAnimating
    变量
@State var isAnimating = false 

然后在外部 VStack 的末尾,在

onAppear
内将此变量设置为 true。然后使用 isAnimating 三元运算符调用
rotationEffect
,然后调用 clal .animation() 。这是完整的代码:

    var body: some View {
        VStack {
            // the trick is to use .animation and some helper variables
            Circle()
               .trim(from: 0.0, to: 0.3)
               .stroke(Color.red, lineWidth: 5)
               .rotationEffect(Angle(degrees: isAnimating ? 360 : 0))
               .animation(Animation.linear(duration:1).repeatForever(autoreverses: false), value: isAnimating)
        } //: VStack
        .onAppear {
            isAnimating = true
        }
    }

这样就不需要使用DispatchQueue.main.async了。


0
投票

我遇到了与您类似的问题,但是在 iOS 17 上使用

NavigationStack
。尽管按照接受的答案中的建议使用
DispatchQueue
提出的解决方案对我有用,但我发现使用
task{}
修饰符而不是
onAppear{}

struct EscapingAnimationTest_Inner: View {
    @State var degrees: CGFloat = 0
    
    var body: some View {
        Circle()
            .trim(from: 0.0, to: 0.3)
            .stroke(Color.red, lineWidth: 5)
            .rotationEffect(Angle(degrees: Double(degrees)))
            .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false), value: degrees)
            .task { // -> use task instead of onAppear
                // no need for DispatchQueue.main.async
                degrees = 360
            }
    }
}

这允许您避免使用 DispatchQueue,使整体解决方案更加“类似 SwiftUI”。尽管如此,我欢迎任何建议或改进。

© www.soinside.com 2019 - 2024. All rights reserved.