【问题标题】:Restarting perpetual animation after stopping in SwiftUI在 SwiftUI 中停止后重新启动永久动画
【发布时间】:2021-07-29 21:05:25
【问题描述】:

背景

在这个学习应用程序中,我遵循了来自Hacking with Swift on generating a wave-like animation 的优秀教程。我已经修改了这个应用程序,进一步添加了一些功能:

  • 为波浪动画提供启动/停止机制
  • 在动画期间不断生成随机数
  • 如果找到“有趣”的数字,则修改动画。最初,我实现了将偶数定义为有趣的逻辑,但可以轻松更改为标记素数等。

问题

停止动画后不会再次“运行”。这在下面的 gif 中进行了演示。

停止动画后不会重新开始。

代码

//
//  ContentView.swift
//  WaveExample
//
//  Created by Konrad on 28/07/2021.
//  Original tutorial: https://www.hackingwithswift.com/plus/custom-swiftui-components/creating-a-waveview-to-draw-smooth-waveforms
//

import SwiftUI

/**
 Creates wave shape object
 - Parameter strength: How tall the wave should be
 - Parameter frequency: How densly the wave should be packed
 - returns: Shape
 */
struct Wave: Shape {
    // Basic wave characteristics
    var strength: Double    // Height
    var frequency: Double   // Number of hills
    var phase: Double       // Offsets the wave, can be used to animate the view

    // Required to define that animation relates to moving the wave from left to right
    var animatableData: Double {
        get { phase }
        set { self.phase = newValue }
    }

    // Path drawing function
    func path(in rect: CGRect) -> Path {
        let path = UIBezierPath()

        // Basic waveline characteristics
        let width = Double(rect.width)
        let height = Double(rect.height)
        let midWidth = width / 2
        let midHeight = height / 2
        let wavelength = width / frequency
        let oneOverMidWidth = 1 / midWidth

        // Path characteristics
        path.move(to: CGPoint(x: 0, y: midHeight))

        // By determines the nmber of calculations, can be decreased to run faster
        for xPosition in stride(from: 0, through: width, by: 1) {
            let relativeX = xPosition / wavelength          // How far we are from the start point
            let distanceFromMidWidth = xPosition - midWidth // Distance from the middle of the space
            let normalDistance = distanceFromMidWidth * oneOverMidWidth // Get values from -1 to 1, normalize
            // let parabola = normalDistance // Small waves in the middle
            let parabola = -(normalDistance * normalDistance) + 1 // Big wave in the middle
            let sine = sin(relativeX + phase)       // Offset based on phase
            let yPosition = parabola * strength * sine + midHeight     // Moving this halfway
            path.addLine(to: CGPoint(x: xPosition, y: yPosition))
        }

        return Path(path.cgPath)
    }
}

struct Line: Shape {
    func path(in rect: CGRect) -> Path {

        // Positioning
        let midHeight = rect.height / 2

        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: midHeight))
        path.addLine(to: CGPoint(x: rect.width, y: midHeight))
        return Path(path.cgPath)
    }
}

struct ContentView: View {

    @State private var phase = 0.0                     // Used to animate the wave
    @State private var waveStrength: Double = 10.0     // How tall, change for interesting numbers
    @State private var waveFrequency: Double = 10.0    // How frequent, change for interesting numbers

    @State var isAnimating: Bool = false    // Currently running animation
    @State private var randNum: Int16 = 0   // Random number to keep generating while animating
    @State private var isNumberInteresting: Bool = false // Will take 'true' of the random number has some interesting properties

    // Timer publisher reflecting frequent animation changes
    @State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    // Stop timer
    func stopTimer() {
        self.timer.upstream.connect().cancel()
    }
    // Start timer
    func startTimer() {
        self.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    }

    // Check if number is interesting
    func checkNumber(num: Int16) -> Bool {
        var isInteresting: Bool = false
        if num % 2 == 0 {
            isInteresting.toggle()
        }
        return isInteresting
    }

    var body: some View {
        VStack {
            if self.isAnimating {
                VStack {
                    Button("Stop") {
                        self.isAnimating = false
                        stopTimer()
                    }
                    .font(.title)
                    .foregroundColor(Color(.blue))

                    Text("Random number: \(String(randNum)), interesting: \(String(isNumberInteresting))")
                        .onReceive(timer, perform: { _ in
                            randNum = Int16.random(in: 0..<Int16.max)
                            isNumberInteresting = checkNumber(num: randNum)
                        })
                }
            } else {
                Button("Start") {
                    self.isAnimating = true
                    startTimer()
                }
                .font(.title)
                .foregroundColor(Color(.red))
            }
            if self.isAnimating {
                // Animation
                ZStack {
                    ForEach(0..<10) { waveIteration in
                        Wave(strength: waveStrength, frequency: waveFrequency, phase: phase)
                            .stroke(Color.blue.opacity(Double(waveIteration) / 3), lineWidth: 1.1)
                            .offset(y: CGFloat(waveIteration) * 10)
                    }
                }
                .onReceive(timer) { _ in
                    // withAnimation requires info on how to animate
                    withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                        self.phase = .pi * 2 // 180 degrees of sine being calculated
                        if isNumberInteresting {
                            waveFrequency = 50.0
                            waveStrength = 50.0
                        } else {
                            waveFrequency = 10.0
                            waveStrength = 10.0
                        }
                    }
                }
                .frame(height: UIScreen.main.bounds.height * 0.8)
            } else {
                // Static line
                ZStack {
                    Line()
                        .stroke(Color.blue)
                }
                .frame(height: UIScreen.main.bounds.height * 0.8)
            }
            Spacer()
        }
    }
}

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

旁注

对于上面的问题,任何关于使用 Swift 的良好实践指针总是受欢迎的。

【问题讨论】:

    标签: swift animation swiftui


    【解决方案1】:

    我让你的项目工作了,你可以看到更改后的代码// &lt;&lt;: Here!,问题是你没有显示动画更改的值!你只展示了一次!之后你保持不变!如果您在问题中看到您的代码,您正在重复self.phase = .pi * 2,这对动画毫无意义!我刚刚处理了您的ContentView,所有项目都需要重构工作,但这不是这里的问题。

    struct ContentView: View {
        
        @State private var phase = 0.0
        @State private var waveStrength: Double = 10.0
        @State private var waveFrequency: Double = 10.0
        
        @State var isAnimating: Bool = false
        @State private var randNum: Int16 = 0
        @State private var isNumberInteresting: Bool = false
        
        
        @State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
        
        
        @State private var stringOfText: String = String()   // <<: Here!
        
        func stopTimer() {
            
            self.timer.upstream.connect().cancel()
            
            phase = 0.0  // <<: Here!
        }
        
        func startTimer() {
            
            self.timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
            
            DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + DispatchTimeInterval.milliseconds(500)) { phase = .pi * 2 }    // <<: Here!
            
        }
        
        
        func checkNumber(num: Int16) -> Bool {
            var isInteresting: Bool = false
            if num % 2 == 0 {
                isInteresting.toggle()
            }
            return isInteresting
        }
        
        
        
        var body: some View {
            
            VStack {
                
                Button(isAnimating ? "Stop" : "Start") {  // <<: Here!
                    
                    isAnimating.toggle()    // <<: Here!
                    
                    isAnimating ? startTimer() : stopTimer()   // <<: Here!
                    
                }
                .font(.title)
                .foregroundColor(isAnimating ? Color.red : Color.blue)  // <<: Here!
                
                
                ZStack {
                    
                    if isAnimating {
                        
                        ForEach(0..<10) { waveIteration in
                            Wave(strength: waveStrength, frequency: waveFrequency, phase: phase)
                                .stroke(Color.blue.opacity(Double(waveIteration) / 3), lineWidth: 1.1)
                                .offset(y: CGFloat(waveIteration) * 10)
                        }
                        
                    }
                    else {
                        
                        Line().stroke(Color.blue)
                        
                    }
                    
                }
                .frame(height: UIScreen.main.bounds.height * 0.8)
                .overlay(isAnimating ? Text(stringOfText) : nil, alignment: .top)   // <<: Here!
                .onReceive(timer) { _ in
    
                    if isAnimating {  // <<: Here!
                        
                        randNum = Int16.random(in: 0..<Int16.max)        
                        isNumberInteresting = checkNumber(num: randNum)  
    
                        stringOfText = "Random number: \(String(randNum)), interesting: \(String(isNumberInteresting))" // <<: Here!
    
                        if isNumberInteresting {
                            waveFrequency = 50.0
                            waveStrength = 50.0
                        } else {
                            waveFrequency = 10.0
                            waveStrength = 10.0
                        }
     
                    }
                    else {
                        stopTimer() // For safety! Killing Timer in case!        // <<: Here!
                    }
      
                }
                .animation(nil, value: stringOfText)  // <<: Here!
                .animation(Animation.linear(duration: 1).repeatForever(autoreverses: false))  // <<: Here!
                .id(isAnimating)  // <<: Here!
                
            }
            
            
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2020-03-26
      • 1970-01-01
      • 1970-01-01
      • 2015-10-03
      • 1970-01-01
      • 2013-10-25
      • 1970-01-01
      • 1970-01-01
      • 2012-01-31
      相关资源
      最近更新 更多