简体   繁体   English

在 SwiftUI 中平滑加速旋转

[英]Smoothly speed up rotation in SwiftUI

I would like to smoothly accelerate the rotation speed of a shape in a SwiftUI application then slow it back down again to fixed speed.我想在 SwiftUI 应用程序中平滑地加快形状的旋转速度,然后再次将其减慢到固定速度。 First I tried toggling the animation speed using a @State Bool as I would any other property (eg .speed(speedUp ? 5.0 : 1.0) ), but I suppose animation properties themselves are not themselves animatable.首先,我尝试使用@State Bool切换动画速度,就像我使用任何其他属性一样(例如.speed(speedUp ? 5.0 : 1.0) ),但我认为动画属性本身不是可动画的。 I've also tried using an AnimatableModifier to no effect:我也试过使用AnimatableModifier没有效果:

import SwiftUI

struct SpeedModifier: AnimatableModifier {
    var speed: Double
    var animatableData: Double {
        get { speed }
        set { speed = newValue }
    }

    func body(content: Content) -> some View {
        return content.animation(
                Animation
                    .linear(duration: 5.0)
                    .speed(speed)
                    .repeatForever(autoreverses: false)
            )
    }
}

struct SwiftUIView: View {
    @State var isRotating = false
    @State var speedUp = false
    var body: some View {
        Rectangle()
            .frame(width: 200, height: 200)
            .rotationEffect(.degrees(isRotating ? 360 : 0))
            .modifier(SpeedModifier(speed: speedUp ? 5.0 : 1.0))
            .onAppear {
                self.isRotating.toggle()
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
                    self.speedUp.toggle()
                }
            }
    }
}

struct SwiftUIView_Previews: PreviewProvider {
    static var previews: some View {
        SwiftUIView()
    }
}

You may try .timingCurve animation.你可以试试.timingCurve动画。 In this example rectangle rotated slower and faster all the time:在这个例子中,矩形一直旋转得越来越慢:

struct RotatingView: View {

    @State private var rotationDegree = 0.0
    private var timeCurveAnimation: Animation {
        return Animation.timingCurve(0.5, 0.8, 0.8, 0.3, duration: 6)
            .repeatForever(autoreverses: false)
    }

    var body: some View {
        Rectangle()
            .frame(width: 200, height: 200)
            .rotationEffect(.degrees(rotationDegree))
            .onAppear() {
                withAnimation(self.timeCurveAnimation) {
                    self.rotationDegree = 720.0
                }
        }
    }

}

Unfortunately there are almost no documentation what does ( c0x:c0y:c1x: c1y: ) parameters mean, tried some samples from this github .不幸的是,几乎没有文档 ( c0x:c0y:c1x: c1y: ) 参数是什么意思,从这个 github尝试了一些示例。 More complex animations are described in this article , it should be useful这篇文章描述更复杂的动画,应该很有用

So far as I've been able to determine, being able to dynamically change a running animation is, as of this writing (iOS 13, OSX 10.15), unfortunately a job for Core Animation, where it's reasonably simple.据我所知,在撰写本文时(iOS 13,OSX 10.15),能够动态更改正在运行的动画是 Core Animation 的一项工作,它相当简单。

For example, using Core Animation, we can add the following animation to a layer, causing it to rotate once every 0.5 seconds, indefinitely.例如,使用 Core Animation,我们可以将以下动画添加到图层,使其每 0.5 秒无限期地旋转一次。

private let animation: CABasicAnimation = {
        
    let animation = CABasicAnimation(keyPath: "transform.rotation")
        
    animation.duration    = 0.5
    animation.fromValue   = 0.0
    animation.toValue     = 2.0 * -Float.pi
    animation.repeatCount = .infinity
        
    return animation
}()

Once you've got that going, then the following extension allows for smooth changing of the layer's speed, and stopping the layer in-situ.完成后,以下扩展允许平滑更改图层的速度,并在原位停止图层。

import QuartzCore

extension CALayer {
    
    // Set up our view of the world such that time began from here,
    // so that we don't feel the need to change anything when our
    // properties are mutated. Handy for things like changing the
    // speed without first snapping back to the model state.
    
    func syncTimeToCurrent() {
        timeOffset = convertTime(CACurrentMediaTime(), from: nil)
        beginTime  = CACurrentMediaTime()
    }

    // Attempt to sync up the model transform with the presentation
    // transform. Handy for preventing the presentation from snapping
    // back to the model after removing a transform animation.
    
    func syncTransformToPresentation() {
        if let presentationTransform = presentation()?.transform {
            transform = presentationTransform
        }
    }
}

And you'd use it like so.你会像这样使用它。 Details of how the view is instantiated omitted;省略了如何实例化视图的细节; in my case, the view is a layer hosting view with 3 image sublayers, two of which are static, and one of which rotates.在我的例子中,视图是一个包含 3 个图像子层的层托管视图,其中两个是静态的,一个是旋转的。

final class Rotating: NSView {

    private let animation: CABasicAnimation = {
        
        let animation = CABasicAnimation(keyPath: "transform.rotation")
        
        animation.duration    = 0.5
        animation.fromValue   = 0.0
        animation.toValue     = 2.0 * -Float.pi
        animation.repeatCount = .infinity
        
        return animation
    }()
    
    private let rotatingLayer: CALayer = { return CALayer() }()

    // Our speed is that of our rotating layer; return it to callers when
    // requested, and allow them to set it.

    var speed: Float {
        get { return rotatingLayer.speed }
        set {
            
            // Starting rotation from a dead stop is just adding
            // the animation to the layer.
            
            func run() {
                rotatingLayer.add(animation, forKey: nil)
            }
            
            // Key to setting the speed, if we are already rotating,
            // is to ensure we don't jump to the start position when
            // we do that, so set up the layer's view of time such
            // that it'll feel there's nothing to do in that regard.
            
            func set() {
                rotatingLayer.syncTimeToCurrent()
            }
            
            // Stopping rotation is just removing the transform
            // animation, but we ideally want to halt it where it is
            // at the moment, rather than having it snap back to the
            // original position.
            
            func off() {
                rotatingLayer.syncTransformToPresentation()
                rotatingLayer.removeAllAnimations()
            }
            
            // If we're being asked to set a zero speed, then it's
            // likely that the caller knows things that we don't,
            // such as that we're about to disappear. Stop rotation,
            // so things are in a well-defined state.
            //
            // If the new speed isn't zero, but our current speed is
            // zero, then we need to run.
            //
            // Otherwise, we need to set the already-running rotation
            // to the new speed.
            
            if      newValue == .zero { off() }
            else if speed    == .zero { run() }
            else                      { set() }
            
            rotatingLayer.speed = newValue
        }
    }
}

So, reasonably simple there, really;所以,那里相当简单,真的; all the tools are there to make things dynamically modifiable in a straightforward manner, so you could just do the same and then import the view into SwiftUI, bind it, etc.所有的工具都可以以一种直接的方式动态地修改东西,所以你可以做同样的事情,然后将视图导入 SwiftUI,绑定它等等。

I'd love to have someone explain how to accomplish the same thing in pure SwiftUI, without having to drop into Core Animation.我很想有人解释如何在纯 SwiftUI 中完成同样的事情,而不必进入 Core Animation。 At the moment, that's how I do it.目前,我就是这样做的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM