如何在 SwiftUI 中停止笔划重复动画

How to stop a stroke repeat animation in SwiftUI

如何通过赋值绑定来控制Rectangle动画重复与否属性isRepeatAnimation?

我期望的是,在将 true 分配给 isRepeatAnimation 后,边框宽度从 5.0 到 0.0 来回动画,并且在分配 false 后重复动画关闭至 isRepeatAnimation.


import SwiftUI

struct ContentView: View {
    @Binding var isRepeatAnimation: Bool
    @State var lineWidth: CGFloat = 5
    
    var body: some View {
        Rectangle()
            .stroke(Color.blue, style: StrokeStyle(lineWidth: lineWidth))
            .frame(width: 100, height: 100)
            .animation(isRepeatAnimation ? repeatAnimation : Animation.easeInOut, value: lineWidth)
    }
    
    var repeatAnimation: Animation {
        Animation.easeInOut.repeatForever(autoreverses: true)
    }
}

struct ContentViewPreviewer: View {
    @State var repeated = false
    var body: some View {
        VStack {
            ContentView(isRepeatAnimation: $repeated)
            Button("Toggle") { 
                repeated.toggle()
            }
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentViewPreviewer()
    }
}

要停止重复动画,我们应该将其替换为默认动画,并且可以切换线宽。

测试 Xcode 13.4 / iOS 15.4

主要修复如下:

Rectangle()
    .stroke(Color.blue, style: StrokeStyle(lineWidth: isRepeatAnimation ? 0 : lineWidth))
    .frame(width: 100, height: 100)
    .animation(isRepeatAnimation ? repeatAnimation : Animation.default, value: isRepeatAnimation)

Complete test module in project

如果目的是让线宽从5到0再到5然后停止,这可以是一个解决方案。如果目的是手动停止动画,请阅读 Asperi 的解决方案。

您可以设置两次动画,首先是在持续时间的前半段将线宽从 5 更改为 0,然后在后半段从 0 更改为 5。您可以使用两个命令控制此行为:

  • .animation() 上,包含一个 pre-defined (duration:)
  • 布尔变量发生变化时,使用 DispatchQueue.main.asyncAfter()
  • 从 0 变回 5

示例代码如下:

struct ContentView: View {
    @Binding var animate: Bool
    @State private var lineWidth = 5.0
    
    var body: some View {
        Rectangle()
        
            .stroke(Color.blue, style: StrokeStyle(lineWidth: lineWidth))
        
            // Instead of repeating, make it with a pre-defined duration.
            // The value here (0.25) is half of the total duration
            .animation(.easeInOut(duration: 0.25), value: lineWidth)
            .frame(width: 100, height: 100)
        
            // Change the line width when animate changes
            .onChange(of: animate) { _ in
                
                // Make it zero immediately
                lineWidth = 0
                
                // On the second half of the duration, make it 5 again
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {
                    lineWidth = 5
                }
            }
    }
}

struct ContentViewPreviewer: View {
    @State var animate = false
    var body: some View {
        VStack {
            ContentView(animate: $animate)
            Button("Toggle") {
                animate.toggle()
            }
        }
    }
}