ios - Shape not re-animating in SwiftUI - Stack Overflow

I have the following wave animation happening and on button press I animate it's strength such tha

I have the following wave animation happening and on button press I animate it's strength such that it flatlines with animation. However, when I press the button again, the wave animation doesn't restart. How do I get it to re-start the animation again?

struct Wave: Shape {
    var strength: Double
    var frequency: Double
    var phase: Double
    var animatableData: AnimatablePair<Double, Double> {
        get { AnimatablePair(phase, strength) }
        set {
            self.phase = newValue.first
            self.strength = newValue.second
        }
    }
    
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let width = Double(rect.width)
        let height = Double(rect.height)
        let midHeight = height / 2
        let wavelength = width / frequency

        let firstX = 0.0
        let firstRelativeX = firstX / wavelength
        let firstSine = sin(firstRelativeX + phase)
        let firstY = strength * firstSine + midHeight
        path.move(to: CGPoint(x: firstX, y: firstY))

        for x in stride(from: 0.0, through: width, by: 1) {
            let relativeX = x / wavelength
            let sine = sin(relativeX + phase)
            let y = strength * sine + midHeight
            path.addLine(to: CGPoint(x: x, y: y))
        }

        return path
    }
}

struct WaveView: View {
    @Binding var isAnimating: Bool
    @State private var phase = 0.0
    @State private var waveStrength = 50.0
    
    var body: some View {
        VStack {
            Wave(strength: waveStrength, frequency: 30, phase: phase)
                .stroke(.black, lineWidth: 5)
                .onChange(of: isAnimating) { _, newValue in
                    withAnimation(.easeInOut(duration: 0.5)) {
                        waveStrength = newValue ? 50.0 : 0.0
                    }
                    if newValue {
                        animateWave()
                    }
                }
                .onAppear {
                    animateWave()
                }
        }
    }
    
    private func animateWave() {
        withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
            self.phase = .pi * 2
        }
    }
}

struct WaveContainerView: View {
    @State var isAnimating = true

    var body: some View {
        VStack {

            WaveView(isAnimating: $isAnimating)
            
            Button("Animate") {
                isAnimating.toggle()
            }
        }
    }
}

I have the following wave animation happening and on button press I animate it's strength such that it flatlines with animation. However, when I press the button again, the wave animation doesn't restart. How do I get it to re-start the animation again?

struct Wave: Shape {
    var strength: Double
    var frequency: Double
    var phase: Double
    var animatableData: AnimatablePair<Double, Double> {
        get { AnimatablePair(phase, strength) }
        set {
            self.phase = newValue.first
            self.strength = newValue.second
        }
    }
    
    func path(in rect: CGRect) -> Path {
        var path = Path()

        let width = Double(rect.width)
        let height = Double(rect.height)
        let midHeight = height / 2
        let wavelength = width / frequency

        let firstX = 0.0
        let firstRelativeX = firstX / wavelength
        let firstSine = sin(firstRelativeX + phase)
        let firstY = strength * firstSine + midHeight
        path.move(to: CGPoint(x: firstX, y: firstY))

        for x in stride(from: 0.0, through: width, by: 1) {
            let relativeX = x / wavelength
            let sine = sin(relativeX + phase)
            let y = strength * sine + midHeight
            path.addLine(to: CGPoint(x: x, y: y))
        }

        return path
    }
}

struct WaveView: View {
    @Binding var isAnimating: Bool
    @State private var phase = 0.0
    @State private var waveStrength = 50.0
    
    var body: some View {
        VStack {
            Wave(strength: waveStrength, frequency: 30, phase: phase)
                .stroke(.black, lineWidth: 5)
                .onChange(of: isAnimating) { _, newValue in
                    withAnimation(.easeInOut(duration: 0.5)) {
                        waveStrength = newValue ? 50.0 : 0.0
                    }
                    if newValue {
                        animateWave()
                    }
                }
                .onAppear {
                    animateWave()
                }
        }
    }
    
    private func animateWave() {
        withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
            self.phase = .pi * 2
        }
    }
}

struct WaveContainerView: View {
    @State var isAnimating = true

    var body: some View {
        VStack {

            WaveView(isAnimating: $isAnimating)
            
            Button("Animate") {
                isAnimating.toggle()
            }
        }
    }
}
Share Improve this question asked Nov 16, 2024 at 3:57 batmanbatman 2,4582 gold badges27 silver badges52 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

To get your animation start again, you need to reset phase value before start animation. After WaveView appear, phase value get set to .pi * 2, then when you press button, you just set it to the same value, SwiftUI do not see any change of phase so your animation is not run, also when onChange closure of isAnimating get called, a new withAnimation block get called therefore current animation repeatForever get cancel because animation for phase and strength are stick together in AnimatablePair:

private func animateWave() {
    phase = 0 // <- reset phase value here
    withAnimation(Animation.linear(duration: 2).repeatForever(autoreverses: false)) {
        self.phase = .pi * 2
    }
}

You can comment out withAnimation block in onChange(of: isAnimating) to keep repeatForever animation, but then you will not have animation when waveStrength change:

.onChange(of: isAnimating) { _, newValue in
    //withAnimation(.easeInOut(duration: 0.5)) {
    waveStrength = newValue ? 50.0 : 0.0
    //}
    if newValue {
        animateWave()
    }
}

Another fix is to seperate phase and waveStrength from AnimatablePair:

struct ShapheWave: View, @preconcurrency Animatable {
    var strength: Double
    var frequency: Double
    var phase: Double
    
    var animatableData: Double {
        get { phase }
        set {
            self.phase = newValue
        }
    }
    
    var body: some View {
        Wave(strength: strength, frequency: frequency, phase: phase)
            .stroke(.black, lineWidth: 5)
    }
}
struct Wave: Shape {
    var strength: Double
    var frequency: Double
    var phase: Double = .pi * 2
    var animatableData: Double {
        get { strength }
        set {
            self.strength = newValue
        }
    }
    
    func path(in rect: CGRect) -> Path {
        var path = Path()
        
        let width = Double(rect.width)
        let height = Double(rect.height)
        let midHeight = height / 2
        let wavelength = width / frequency
        
        let firstX = 0.0
        let firstRelativeX = firstX / wavelength
        let firstSine = sin(firstRelativeX + phase)
        let firstY = strength * firstSine + midHeight
        path.move(to: CGPoint(x: firstX, y: firstY))
        
        for x in stride(from: 0.0, through: width, by: 1) {
            let relativeX = x / wavelength
            let sine = sin(relativeX + phase)
            let y = strength * sine + midHeight
            path.addLine(to: CGPoint(x: x, y: y))
        }
        
        return path
    }
}

struct WaveView: View {
    @Binding var isAnimating: Bool
    @State private var phase = 0.0
    @State private var waveStrength = 50.0
    
    var body: some View {
        VStack {
            ShapheWave(strength: waveStrength, frequency: 30, phase: phase)
                .onChange(of: isAnimating) { _, newValue in
                    waveStrength = newValue ? 50.0 : 0.0
                }
                .onAppear {
                    animateWave()
                }
                .animation(.linear(duration: 2).repeatForever(autoreverses: false), value: phase)              .animation(.easeInOut(duration: 0.5), value: waveStrength)
        }
    }
    
    private func animateWave() {
        phase = .pi * 2
    }
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1745665856a4639118.html

相关推荐

  • ios - Shape not re-animating in SwiftUI - Stack Overflow

    I have the following wave animation happening and on button press I animate it's strength such tha

    5小时前
    20

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信