最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

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

programmeradmin1浏览0评论

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
    }
}
发布评论

评论列表(0)

  1. 暂无评论