当我在 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()
}
}
这是固定部分(我的另一个带有解释的答案在这里)。
使用 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
}
}
}
在
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了。
我遇到了与您类似的问题,但是在 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”。尽管如此,我欢迎任何建议或改进。