如何在 iOS Swift 中制作加载动画?

Posted

技术标签:

【中文标题】如何在 iOS Swift 中制作加载动画?【英文标题】:How to make loading animation in iOS Swift? 【发布时间】:2020-06-05 05:49:26 【问题描述】:

嗨,我想用 3 UIView 制作动画。主要问题是我无法通过从layer 中删除动画来停止动画。

代码如下:

class HTAnimatedTypingView: UIView 

    @IBOutlet weak var view1: UIView!
    @IBOutlet weak var view2: UIView!
    @IBOutlet weak var view3: UIView!

    func startAnimation()

        UIView.animate(withDuration: 0.5, delay: 0, options: .repeat, animations: 

        self.view1.frame.origin.y = 0

       , completion: nil)

        UIView.animate(withDuration: 0.3, delay: 0.5, options: .repeat, animations: 

               self.view2.frame.origin.y = 0

              , completion: nil)

        UIView.animate(withDuration: 0.2, delay: 1.0, options: .repeat, animations: 

                     self.view3.frame.origin.y = 0

                    , completion: nil)

    

    func stopAnimations()

        self.view1.layer.removeAllAnimations()
        self.view2.layer.removeAllAnimations()
        self.view3.layer.removeAllAnimations()

    

以上代码的输出:

预期动画:

如何使它与开始动画和停止动画功能一起使用?提前谢谢...

【问题讨论】:

【参考方案1】:

由于您需要在每个动画序列之间添加一些暂停,我个人会使用 关键帧 来完成,因为它为您提供了一些灵活性:

class AnimationViewController: UIViewController 

    private let stackView: UIStackView = 
        $0.distribution = .fill
        $0.axis = .horizontal
        $0.alignment = .center
        $0.spacing = 10
        return $0
    (UIStackView())

    private let circleA = UIView()
    private let circleB = UIView()
    private let circleC = UIView()
    private lazy var circles = [circleA, circleB, circleC]

    func animate() 
        let jumpDuration: Double = 0.30
        let delayDuration: Double = 1.25
        let totalDuration: Double = delayDuration + jumpDuration*2

        let jumpRelativeDuration: Double = jumpDuration / totalDuration
        let jumpRelativeTime: Double = delayDuration / totalDuration
        let fallRelativeTime: Double = (delayDuration + jumpDuration) / totalDuration

        for (index, circle) in circles.enumerated() 
            let delay = jumpDuration*2 * TimeInterval(index) / TimeInterval(circles.count)
            UIView.animateKeyframes(withDuration: totalDuration, delay: delay, options: [.repeat], animations: 
                UIView.addKeyframe(withRelativeStartTime: jumpRelativeTime, relativeDuration: jumpRelativeDuration) 
                    circle.frame.origin.y -= 30
                
                UIView.addKeyframe(withRelativeStartTime: fallRelativeTime, relativeDuration: jumpRelativeDuration) 
                    circle.frame.origin.y += 30
                
            )
        
    

    override func viewDidLoad() 
        super.viewDidLoad()
        view.backgroundColor = .white
        view.addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        circles.forEach 
            $0.layer.cornerRadius = 20/2
            $0.layer.masksToBounds = true
            $0.backgroundColor = .systemBlue
            stackView.addArrangedSubview($0)
            $0.widthAnchor.constraint(equalToConstant: 20).isActive = true
            $0.heightAnchor.constraint(equalTo: $0.widthAnchor).isActive = true
        
    

    override func viewDidAppear(_ animated: Bool) 
        super.viewDidAppear(animated)
        animate()
    

应该很简单,但如果您有任何问题,请随时告诉我!

结果如下所示:

【讨论】:

【参考方案2】:

一种方法是使用Timer。在您的班级中保留Timer 的实例。当startAnimation 被调用时,安排它。当stopAnimation 被调用时,invalidate 它。 (这意味着当前正在进行的动画将在动画实际停止之前完成,IMO 使它成为一个不错的非突然停止)。

在计时器的每个滴答声中,为点设置一次动画。请注意,您在每个点上应用的动画应该具有相同的持续时间,就像在预期的输出中一样,它们都以相同的速率反弹,只是在不同的时刻。

一些说明性代码:

// startAnimation
timer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true)  _ in
    self.animateDotsOnce()


// stopAnimation
timer.invalidate()

// animateDotsOnce
UIView.animate(withDuration: animationDuration, delay: 0, animations: 

    self.view1.frame.origin.y = animateHeight

, completion: 
    _ in
    UIView.animate(withDuration: animationDuration) 
        self.view1.frame.origin.y = 0
    
)

// plus the other two views, with different delays...

我会让你为每个视图找到合适的animateHeighttimerIntervalanimationDuration 和延迟。

【讨论】:

谢谢!,效果很好!;我将 view2、view3 的延迟设置为 0.2、0.4,计时器间隔为 0.5(动画持续时间)+延迟 = 1.1;这符合我的预期! 我绝对不推荐使用定时器来完成这样的任务,因为它可以通过专用 API 以更高效的方式轻松完成:UIView.animateKeyframes。 @AlexLinares 是的,你说得对...我不熟悉animateKeyframes...【参考方案3】:

我建议使用CAKeyframeAnimation 而不是处理完成块和那个巫术。这是一个简单的例子:

for i in 0 ..< 3 
    let bubble = UIView(frame: CGRect(x: 20 + i * 20, y: 200, width: 10, height: 10))
    bubble.backgroundColor = .red
    bubble.layer.cornerRadius = 5
    self.view.addSubview(bubble)

    let animation = CAKeyframeAnimation()
    animation.keyPath = "position.y"
    animation.values = [0, 10, 0]
    animation.keyTimes = [0, 0.5, 1]
    animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
    animation.duration = 1
    animation.isAdditive = true
    animation.repeatCount = HUGE
    animation.timeOffset = CACurrentMediaTime() + 0.2 * Double(i)

    bubble.layer.add(animation, forKey: "anim")


当您想删除动画时,您只需使用bubble.layer.removeAnimation(forKey: "anim")。您可能需要使用计时功能或valueskeyTimes 来获得您想要的精确运动。但是关键帧是制作特定动画的方法。

旁注:这个例子在viewDidLoad 中不起作用,因为视图还没有超级视图,所以动画不起作用。如果你在viewDidAppear 中测试它,它会起作用。

【讨论】:

【参考方案4】:

UIView 动画与 CALayer 动画不同,最好不要混用。 在本地编写并经过测试。

import UIKit
import SnapKit

class HTAnimatedTypingView: UIView 

    private let view0 = UIView()
    private let view1 = UIView()
    private let view2 = UIView()

    init() 
        super.init(frame: CGRect.zero)
        makeUI()
    

    override init(frame: CGRect) 
        super.init(frame: frame)
        makeUI()
    

    required init?(coder: NSCoder) 
        super.init(coder: coder)
        makeUI()
    

    private func makeUI() 

        backgroundColor = UIColor.white

        view0.backgroundColor = UIColor.red
        view1.backgroundColor = UIColor.blue
        view2.backgroundColor = UIColor.yellow

        addSubview(view0)
        addSubview(view1)
        addSubview(view2)

        view0.snp.makeConstraints  (make) in
            make.centerY.equalTo(self.snp.centerY)
            make.width.equalTo(10)
            make.height.equalTo(10)
            make.left.equalTo(self.snp.left)
        

        view1.snp.makeConstraints  (make) in
            make.centerY.equalTo(self.snp.centerY)
            make.width.equalTo(10)
            make.height.equalTo(10)
            make.centerX.equalTo(self.snp.centerX)
        

        view2.snp.makeConstraints  (make) in
            make.centerY.equalTo(self.snp.centerY)
            make.width.equalTo(10)
            make.height.equalTo(10)
            make.right.equalTo(self.snp.right)
        

    

    public func startAnimation() 

        let duration:CFTimeInterval = 0.5
        let animation_delay:CFTimeInterval = 0.1

        assert(duration >= animation_delay * 5, "animation_delay should be way smaller than duration in order to make animation natural")

        let translateAnimation = CABasicAnimation(keyPath: "position.y")
        translateAnimation.duration = duration
        translateAnimation.repeatCount = Float.infinity
        translateAnimation.toValue = 0
        translateAnimation.fillMode = CAMediaTimingFillMode.both
        translateAnimation.isRemovedOnCompletion = false
        translateAnimation.autoreverses = true

        view0.layer.add(translateAnimation, forKey: "translation")
        DispatchQueue.main.asyncAfter(deadline: .now() + animation_delay)  [unowned self ] in
            self.view1.layer.add(translateAnimation, forKey: "translation")
        
        DispatchQueue.main.asyncAfter(deadline: .now() + animation_delay * 2)  [unowned self ] in
            self.view2.layer.add(translateAnimation, forKey: "translation")
        

    

    public func stopAnimation() 
        self.view0.layer.removeAllAnimations()
        self.view1.layer.removeAllAnimations()
        self.view2.layer.removeAllAnimations()
    


【讨论】:

以上是关于如何在 iOS Swift 中制作加载动画?的主要内容,如果未能解决你的问题,请参考以下文章

如何从 macOS Swift 上的文档目录加载图像?

自己制作动图怎么加LOGO或者水印

如何在swift 3中重新加载没有动画的特定单元格?

Unity3D序列帧动画制作方法---实现加载进度条

如何在等待程序启动混乱/GTK +时制作加载/介绍动画弹出窗口

加载动画仅在按钮功能后显示(Swift)