在SwiftUI中平稳加速旋转

时间:2020-01-05 06:44:28

标签: ios swift swiftui

我想在SwiftUI应用程序中平稳地加快形状的旋转速度,然后将其再次降低到固定速度。首先,我尝试使用@State Bool来切换动画速度,就像处理其他任何属性(例如.speed(speedUp ? 5.0 : 1.0))一样,但是我认为动画属性本身不是可动画的。我也尝试过使用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()
    }
}

2 个答案:

答案 0 :(得分:1)

您可以尝试制作const siteBucket = new s3.Bucket(this, 'SiteBucket', { bucketName: siteDomain, websiteIndexDocument: 'index.html', websiteErrorDocument: 'error.html', publicReadAccess: true, removalPolicy: cdk.RemovalPolicy.DESTROY, }); new cdk.CfnOutput(this, 'Bucket', {value: siteBucket.bucketName}); const wwwRedirectBucket = new s3.Bucket(this, 'WwwBucket', { bucketName: wwwDomain, websiteRedirect: {hostName: siteDomain, protocol: RedirectProtocol.HTTPS}, publicReadAccess: true, removalPolicy: cdk.RemovalPolicy.DESTROY, }); const certificateArn = "arn:aws:acm:etcetc" const distribution = new cloudfront.CloudFrontWebDistribution(this, 'SiteDistribution', { aliasConfiguration: { acmCertRef: certificateArn, names: [siteDomain], sslMethod: cloudfront.SSLMethod.SNI, securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1_1_2016, }, originConfigs: [ { s3OriginSource: { s3BucketSource: siteBucket }, behaviors: [{isDefaultBehavior: true}], } ] }); const wwwDistribution = new cloudfront.CloudFrontWebDistribution(this, 'WwwDistribution', { aliasConfiguration: { acmCertRef: certificateArn, names: [wwwDomain], sslMethod: cloudfront.SSLMethod.SNI, securityPolicy: cloudfront.SecurityPolicyProtocol.TLS_V1_1_2016, }, originConfigs: [ { s3OriginSource: { s3BucketSource: wwwRedirectBucket }, behaviors: [{isDefaultBehavior: true}], } ] }); 动画。在此示例中,矩形一直旋转得越来越慢:

.timingCurve

不幸的是,几乎没有文档记录了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 } } } } 参数的含义,尝试了this github中的一些示例。 in this article描述了更复杂的动画,它应该很有用

答案 1 :(得分:1)

就我所能确定的,截至撰写本文时(iOS 13,OSX 10.15),能够动态更改正在运行的动画,不幸的是,Core Animation的工作相当简单。

例如,使用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
}()

一旦完成,接下来的扩展将允许平滑更改图层的速度,并就地停止图层。

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
        }
    }
}

您将像这样使用它。实例化视图的细节被省略;就我而言,该视图是一个托管视图的图层,其中包含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
        }
    }
}

因此,实际上相当简单。所有工具都可以使事情以直接的方式动态地修改,因此您可以执行相同的操作,然后将视图导入SwiftUI,绑定它,等等。

我很乐意让某人解释如何在纯SwiftUI中完成相同的事情,而不必使用Core Animation。此刻,我就是这样做的。