我试着做这个粒子动画,想改变 colored颜色 属性.每次我这样做,动画都会加速.我如何防止这种情况发生?我对这有点陌生,有没有更好的方法?

这是我的emits 器


struct EmitterView: View {
    
    private struct ParticleView: View {
        
        let image: Image
        
        @State private var isActive = false
        let position: ParticleState<CGPoint>
        let opacity: ParticleState<Double>
        let rotation: ParticleState<Angle>
        let scale: ParticleState<CGFloat>
        
        var body: some View {
            image
                .opacity(isActive ? opacity.end : opacity.start)
                .scaleEffect(isActive ? scale.end : scale.start)
                .rotationEffect(isActive ? rotation.end : rotation.start)
                .position(isActive ? position.end : position.start)
                .onAppear{self.isActive = true}
        }
    }
    
    private struct ParticleState<T> {
        var start: T
        var end: T
        
        init(_ start: T, _ end: T) {
            self.start = start
            self.end = end
        }
    }
    
    var images: [String]
    
    var particleCount: Int
    
    var creationPoint = UnitPoint.center
    var creationRange = CGSize.zero
    
    var colors = [Color.white]
    var blendMode = BlendMode.normal
    
    var angle = Angle.zero
    var angleRange = Angle.zero
    
    var opacity = 1.0
    var opacityRange = 0.0
    var opacitySpeed = 0.0
    
    var rotation = Angle.zero
    var rotationRange = Angle.zero
    var rotationSpeed = Angle.zero
    
    var scale: CGFloat = 1
    var scaleRange: CGFloat = 0
    var scaleSpeed: CGFloat = 0
    
    var speed = 0.0
    var speedRange = 0.0
    
    var animation = Animation.linear.repeatForever(autoreverses: false)
    var animationDelayTreshold = 0.0
        
    var body: some View {
        
        GeometryReader { geo in
            ZStack {
                ForEach(0..<self.particleCount, id: \.self) { i in
                    
                    ParticleView(
                        image: Image(images.randomElement()!),
                        position: self.position(in: geo),
                        opacity: self.makeOpacity(),
                        rotation: self.makeRotation(),
                        scale: self.makeScale()
                    )
                    
                    .animation(self.animation.delay(Double.random(in: 0...self.animationDelayTreshold)))
                    .colorMultiply(self.colors.randomElement() ?? .white)
                    .blendMode(self.blendMode)
                }
            }
        }
    }
    
    private func position(in proxy: GeometryProxy) -> ParticleState<CGPoint> {
        let halfCreationRangeWidth = creationRange.width / 2
        let halfCreationRangeHeight = creationRange.height / 2
        
        let creationOffsetX = CGFloat.random(in: -halfCreationRangeWidth...halfCreationRangeWidth)
        let creationOffsetY = CGFloat.random(in: -halfCreationRangeHeight...halfCreationRangeHeight)
        
        let startX = (proxy.size.width * (creationPoint.x + creationOffsetX))
        let startY = (proxy.size.height * (creationPoint.y + creationOffsetY))
        let start = CGPoint(x: startX, y: startY)
        
        let halfSpeedRange = speedRange / 2
        let actualSpeed  = Double.random(in: speed - halfSpeedRange...speed + halfSpeedRange)
        
        let halfAngleRange = angleRange.radians / 2
        let totalRange = Double.random(in: angle.radians - halfAngleRange...angle.radians + halfAngleRange)
        
        let finalX = cos(totalRange - .pi / 2) * actualSpeed
        let finalY = sin(totalRange - .pi / 2) * actualSpeed
        let end = CGPoint(x: Double(startX) + finalX, y: Double(startY) + finalY)
        
        return ParticleState(start, end)
    }
    
    private func makeOpacity() -> ParticleState<Double> {
        let halfOpacityRange = opacity / 2
        let randomOpacity = Double.random(in: -halfOpacityRange...halfOpacityRange)
        
        return ParticleState(opacity + randomOpacity, opacity + opacitySpeed + randomOpacity)
    }
    
    private func makeScale() -> ParticleState<CGFloat> {
        let halfScaleRange = scaleRange / 2
        let randomScale = CGFloat.random(in: -halfScaleRange...halfScaleRange)
        
        return ParticleState(scale + randomScale, scale + scaleSpeed + randomScale)
    }
    
    private func makeRotation() -> ParticleState<Angle> {
        let halfRotationRange = (rotationRange / 2).radians
        let randomRotation = Double.random(in: -halfRotationRange...halfRotationRange)
        let randomRotationAngle = Angle(radians: randomRotation)
        
        return ParticleState(rotation + randomRotationAngle, rotation + rotationSpeed + randomRotationAngle)
    }
    
    mutating func makeRed() {
        colors = [.red]
    }
    
}

我就是这样实现的

import SwiftUI

struct ContentView: View {
    
    @State var emitter =  EmitterView(images: ["spark"], particleCount: 200, creationRange: CGSize(width: 0.4, height: 0.2), colors: [.white], blendMode: .screen, angle: .degrees(0), angleRange: .degrees(360), opacityRange: 0, opacitySpeed: 15, scale: 0.5, scaleRange: 0.2, scaleSpeed: -0.2, speed: 50, speedRange: 120, animation: Animation.linear(duration: 1).repeatForever(autoreverses: false), animationDelayTreshold: 1)
    
    
    var body: some View {
        
            ZStack {
                emitter
                    .ignoresSafeArea()
                }
            .background(.black)
            .edgesIgnoringSafeArea(.all)
            .statusBar(hidden: true)
            .onTapGesture {
                    emitter.makeRed()
            }
        }
    }

我也try 了事务,但无法使其工作,动画无法重新启动.

推荐答案

视图应该在主体中,动画应该连接到相应的触发状态.

查找以下固定部件.使用Xcode 13.4/iOS 15.5测试

demo

    @State var colors = [Color.white]  // data !!
    var body: some View {

        ZStack {
            // view is here !!
            EmitterView(images: ["spark"], particleCount: 200, creationRange: CGSize(width: 0.4, height: 0.2), colors: colors, blendMode: .screen, angle: .degrees(0), angleRange: .degrees(360), opacityRange: 0, opacitySpeed: 15, scale: 0.5, scaleRange: 0.2, scaleSpeed: -0.2, speed: 50, speedRange: 120, animation: Animation.linear(duration: 1).repeatForever(autoreverses: false), animationDelayTreshold: 1)
                .ignoresSafeArea()
        }
        .background(.black)
        .edgesIgnoringSafeArea(.all)
        .statusBar(hidden: true)
        .onTapGesture {
            colors = [.red]    // update !!
        }
    }

动画触发器在哪里

    private struct ParticleView: View {

        let image: Image

        @State private var isActive = false
        let position: ParticleState<CGPoint>
        let opacity: ParticleState<Double>
        let rotation: ParticleState<Angle>
        let scale: ParticleState<CGFloat>

        var animation: Animation
        var delayTreshold = 0.0


        var body: some View {
            image
                .opacity(isActive ? opacity.end : opacity.start)
                .scaleEffect(isActive ? scale.end : scale.start)
                .rotationEffect(isActive ? rotation.end : rotation.start)
                .position(isActive ? position.end : position.start)
                // here is animation, depends on isActive !!
                .animation(self.animation.delay(Double.random(in: 0...self.delayTreshold)), value: isActive)
                .onAppear{self.isActive = true}
        }

    }

Complete test module is here

Swift相关问答推荐

VisionOS发送通知

减go 用SWIFT struct 填充的NSCountedSet

Swift-Can无法在AudioKit中找出简单的麦克风效果-文件链

如何在SWIFT Memory中处理对VAR数组的更新(对COW感到困惑)

允许视图在视图内更改

使用UICollectionViewFlowLayout创建水平的UICollectionView,并同时将单元格对齐到左边和右边的能力

带有可选字符串作为键路径的 SwiftUI iOS16 表

快速更改tableView中的数据

SwiftUI View Builder 参数无法更改状态

ZStack 中的 ProgressView 与 View 的行为不同

为什么 Swift Tuples 只能在元素个数小于等于 6 的情况下进行比较?

Vapor, fluent 使用 PostgreSQL 保存/创建复杂模型

Swift:连接两个数组而不重复共享后缀/前缀

如何使用 Date.ParseStrategy 在 Swift 5.6 中将字符串解析为日期?

What is "SwiftPM.SPMRepositoryError error 5"?

理解 Swift 2.2 Select 器语法 - #selector()

使用 swift 运行 pod install 时出错

FCM 后台通知在 iOS 中不起作用

如何实现 Swift 的 Comparable 协议?

Swift 2.0 按属性对对象数组进行排序