系统学习iOS动画—— BahamaAirLogin(动画的Keys和代理)

系统学习iOS动画—— BahamaAirLogin(动画的Keys和代理)

关于视图动画和相应的闭包语法的一个棘手问题是,一旦您创建并运行视图动画,您就无法暂停,停止或以任何方式访问它。

但是,使用核心动画,您可以轻松检查在图层上运行的动画,并在需要时停止它们。 此外,您甚至可以在动画上设置委托对象并对动画事件做出反应。

接着上一篇文章的工程,这里先添加一个属性info。

let info = UILabel()

在viewDidLoad里面设置好info的属性

info.frame = CGRect(x: 0.0, y: loginButton.center.y + 60.0,  width: view.frame.size.width, height: 30)
info.backgroundColor = UIColor.clear
info.font = UIFont(name: "HelveticaNeue", size: 12.0)
info.textAlignment = .center
info.textColor = UIColor.white
info.text = "Tap on a field and enter username and password"
view.insertSubview(info, belowSubview: loginButton)

在viewDidAppear里面为info添加一个位置变化以及透明度变化的动画。

  let flyLeft = CABasicAnimation(keyPath: "position.x")
    flyLeft.fromValue = info.layer.position.x + view.frame.size.width
    flyLeft.toValue = info.layer.position.x
    flyLeft.duration = 5.0
    info.layer.add(flyLeft, forKey: "infoappear")

    let fadeLabelIn = CABasicAnimation(keyPath: "opacity")
    fadeLabelIn.fromValue = 0.2
    fadeLabelIn.toValue = 1.0
    fadeLabelIn.duration = 4.5
    info.layer.add(fadeLabelIn, forKey: "fadein")

接下来我们想要在点击的usernameTextField或者passwordTextField把info的动画移除掉,那么我们就要感知到usernameTextField和passwordTextField被点击了,这里使用UITextFieldDelegate里面的textFieldDidBeginEditing来实现。
在viewDidLoad里面将usernameTextField和passwordTextField 的delegate设为self。

usernameTextField.delegate = self
passwordTextField.delegate = self

然后在textFieldDidBeginEditing回调中移除info的动画。

func textFieldDidBeginEditing(_ textField: UITextField) {
    guard let runningAnimations = info.layer.animationKeys() else {
      return
    }
    print(runningAnimations)
    info.layer.removeAnimation(forKey: "infoappear")
    info.layer.removeAnimation(forKey: "fadein")
  }

我们想要标题在移动的动画完成后做一个缩放的动画,我们当然可以使用 DispatchQueue.main.asyncAfter来实现,但是实际上如果用CAAnimationDelegate的话会更好
将flyRight的delegate设为self。

flyRight.delegate = self

遵守CAAnimationDelegate协议然后使用animationDidStop来完成想要的效果:

extension ViewController: CAAnimationDelegate {
  func animationDidStop(_ anim: CAAnimation,
                        finished flag: Bool) {
    print("animation did finish")

    guard let name = anim.value(forKey: "name") as? String else {
      return
    }

    if name == "form" {
      //form field found

      let layer = anim.value(forKey: "layer") as? CALayer
      anim.setValue(nil, forKey: "layer")

      let pulse = CABasicAnimation(keyPath: "transform.scale")
      pulse.fromValue = 1.25
      pulse.toValue = 1.0
      pulse.duration = 0.25
      layer?.add(pulse, forKey: nil)
    }
  }
}

那么也可以修改animateCloud,让其使用layer animation 结合animationDidStop来达成动画效果,而不用在UIView 的completion block里面重新调用animateCloud执行动画。
animateCloud修改成:

 func animateCloud(layer: CALayer) {

      //1
      let cloudSpeed = 60.0 / Double(view.layer.frame.size.width)
      let duration: TimeInterval = Double(view.layer.frame.size.width - layer.frame.origin.x) * cloudSpeed

      //2
      let cloudMove = CABasicAnimation(keyPath: "position.x")
      cloudMove.duration = duration
      cloudMove.toValue = self.view.bounds.size.width + layer.bounds.width/2
      cloudMove.delegate = self
      cloudMove.fillMode = .forwards
      cloudMove.setValue("cloud", forKey: "name")
      cloudMove.setValue(layer, forKey: "layer")
      layer.add(cloudMove, forKey: nil)
    }

animationDidStop里面添加:

  if name == "cloud" {
      if let layer = anim.value(forKey: "layer") as? CALayer {
        anim.setValue(nil, forKey: "layer")

        layer.position.x = -layer.bounds.width/2
        delay(0.5) {
            self.animateCloud(layer: layer)
        }
      }
    }

完整代码:

import UIKit

class ViewController: UIViewController {
        
    let screenWidth = UIScreen.main.bounds.size.width
    let screenHeight = UIScreen.main.bounds.size.height
    let titleLabel = UILabel()
    let backgroundImage = UIImageView()
    let usernameTextField = TextField()
    let passwordTextField = TextField()
    let loginButton = UIButton()
    let cloud1 = UIImageView()
    let cloud2 = UIImageView()
    let cloud3 = UIImageView()
    let cloud4 = UIImageView()
    let spinner = UIActivityIndicatorView(style: .whiteLarge)
    
    let status = UIImageView(image: UIImage(named: "banner"))
    let label = UILabel()
    let info = UILabel()

    let messages = ["Connecting ...", "Authorizing ...", "Sending credentials ...", "Failed"]
    var statusPosition = CGPoint.zero


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        view.addSubview(backgroundImage)
        view.addSubview(titleLabel)
        view.addSubview(usernameTextField)
        view.addSubview(passwordTextField)
        view.addSubview(loginButton)
        view.addSubview(cloud1)
        view.addSubview(cloud2)
        view.addSubview(cloud3)
        view.addSubview(cloud4)
        loginButton.addSubview(spinner)
        
        let textFieldWidth = screenWidth - 60
        let buttonWidth = 260
        
        backgroundImage.image = UIImage(named: "bg-sunny")
        backgroundImage.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
      
        titleLabel.text = "Bahama Login"
        titleLabel.textColor = .white
        titleLabel.font = UIFont.systemFont(ofSize: 28)
        let titleWidth = titleLabel.intrinsicContentSize.width
        titleLabel.frame = CGRect(x: (screenWidth - titleWidth) / 2 , y: 120, width: titleWidth, height: titleLabel.intrinsicContentSize.height)
        
        usernameTextField.backgroundColor = .white
        usernameTextField.layer.cornerRadius = 5
        usernameTextField.placeholder = "  Username"
        usernameTextField.delegate = self
        usernameTextField.frame = CGRect(x: 30, y: 202, width: textFieldWidth, height: 40)
        
        passwordTextField.backgroundColor = .white
        passwordTextField.layer.cornerRadius = 5
        passwordTextField.placeholder = "  Password"
        passwordTextField.delegate = self
        passwordTextField.frame = CGRect(x: 30, y: 263, width: textFieldWidth, height: 40)
        
        loginButton.frame = CGRect(x: (Int(screenWidth) - buttonWidth) / 2, y: 343, width: buttonWidth, height: 50)
        loginButton.setTitle("Login", for: .normal)
        loginButton.setTitleColor(.red, for: .normal)
        loginButton.layer.cornerRadius = 5
        loginButton.backgroundColor = UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)
        loginButton.addTarget(self, action: #selector(handleLogin), for: .touchUpInside)
        
        spinner.frame = CGRect(x: -20.0, y: 6.0, width: 20.0, height: 20.0)
        spinner.startAnimating()
        spinner.alpha = 0.0
        
        
        cloud1.frame = CGRect(x: -120, y: 79, width: 160, height: 50)
        cloud1.image = UIImage(named: "bg-sunny-cloud-1")
        
        cloud2.frame = CGRect(x: 256, y: 213, width: 160, height: 50)
        cloud2.image = UIImage(named: "bg-sunny-cloud-2")
        
        
        cloud3.frame = CGRect(x: 284, y: 503, width: 74, height: 35)
        cloud3.image = UIImage(named: "bg-sunny-cloud-3")
        
        
        cloud4.frame = CGRect(x:22 , y: 545, width: 115, height: 50)
        cloud4.image = UIImage(named: "bg-sunny-cloud-4")
        
        status.isHidden = true
        status.center = loginButton.center
        view.addSubview(status)

        label.frame = CGRect(x: 0.0, y: 0.0, width: status.frame.size.width, height: status.frame.size.height)
        label.font = UIFont(name: "HelveticaNeue", size: 18.0)
        label.textColor = UIColor(red: 0.89, green: 0.38, blue: 0.0, alpha: 1.0)
        label.textAlignment = .center
        status.addSubview(label)

        statusPosition = status.center
        
        info.frame = CGRect(x: 0.0, y: loginButton.center.y + 60.0,  width: view.frame.size.width, height: 30)
        info.backgroundColor = UIColor.clear
        info.font = UIFont(name: "HelveticaNeue", size: 12.0)
        info.textAlignment = .center
        info.textColor = UIColor.white
        info.text = "Tap on a field and enter username and password"
        view.insertSubview(info, belowSubview: loginButton)
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

       
        
        let flyRight = CABasicAnimation(keyPath: "position.x")
        flyRight.fromValue = -view.bounds.size.width/2
        flyRight.toValue = view.bounds.size.width/2
        flyRight.duration = 0.5
        titleLabel.layer.add(flyRight, forKey: nil)

        flyRight.beginTime = CACurrentMediaTime() + 0.3
        flyRight.fillMode = .both
        usernameTextField.layer.add(flyRight, forKey: nil)

        flyRight.beginTime = CACurrentMediaTime() + 0.4
        passwordTextField.layer.add(flyRight, forKey: nil)
        
        loginButton.center.y += 30.0
        loginButton.alpha = 0.0
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        let fadeIn = CABasicAnimation(keyPath: "opacity")
        fadeIn.fromValue = 0.0
        fadeIn.toValue = 1.0
        fadeIn.duration = 0.5
        fadeIn.fillMode = .backwards
        fadeIn.beginTime = CACurrentMediaTime() + 0.5
        cloud1.layer.add(fadeIn, forKey: nil)

        fadeIn.beginTime = CACurrentMediaTime() + 0.7
        cloud2.layer.add(fadeIn, forKey: nil)

        fadeIn.beginTime = CACurrentMediaTime() + 0.9
        cloud3.layer.add(fadeIn, forKey: nil)

        fadeIn.beginTime = CACurrentMediaTime() + 1.1
        cloud4.layer.add(fadeIn, forKey: nil)
        UIView.animate(withDuration: 0.5, delay: 0.5, usingSpringWithDamping: 0.5,
          initialSpringVelocity: 0.0,
          animations: {
            self.loginButton.center.y -= 30.0
            self.loginButton.alpha = 1.0
          },
          completion: nil
        )
        
        animateCloud(layer: cloud1.layer)
        animateCloud(layer: cloud2.layer)
        animateCloud(layer: cloud3.layer)
        animateCloud(layer: cloud4.layer)
        
        let flyLeft = CABasicAnimation(keyPath: "position.x")
        flyLeft.fromValue = info.layer.position.x + view.frame.size.width
        flyLeft.toValue = info.layer.position.x
        flyLeft.duration = 5.0
        info.layer.add(flyLeft, forKey: "infoappear")

        let fadeLabelIn = CABasicAnimation(keyPath: "opacity")
        fadeLabelIn.fromValue = 0.2
        fadeLabelIn.toValue = 1.0
        fadeLabelIn.duration = 4.5
        info.layer.add(fadeLabelIn, forKey: "fadein")
    }
    
    @objc func handleLogin() {
        view.endEditing(true)
        
        UIView.animate(withDuration: 1.5, delay: 0.0, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: {
          self.loginButton.bounds.size.width += 80.0
        }, completion: nil)
        
        UIView.animate(withDuration: 0.33, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.0, options: [], animations: {
          self.loginButton.center.y += 60.0
          self.spinner.center = CGPoint(
            x: 40.0,
            y: self.loginButton.frame.size.height/2
          )
          self.spinner.alpha = 1.0
        }, completion: { _ in
            self.showMessage(index:0)
        })
        
        let tintColor = UIColor(red: 0.85, green: 0.83, blue: 0.45, alpha: 1.0)
        tintBackgroundColor(layer: loginButton.layer, toColor: tintColor)
        roundCorners(layer: loginButton.layer, toRadius: 25.0)
    }
    func showMessage(index: Int) {
      label.text = messages[index]

      UIView.transition(with: status, duration: 0.33, options: [.curveEaseOut, .transitionFlipFromTop], animations: {
        self.status.isHidden = false
      }, completion: { _ in
        //transition completion
        delay(2.0) {
          if index < self.messages.count-1 {
            self.removeMessage(index: index)

          } else {
            self.resetForm()
          }
        }
      })
    }
    
    func removeMessage(index: Int) {
      UIView.animate(withDuration: 0.33, delay: 0.0, options: [], animations: {
        self.status.center.x += self.view.frame.size.width
      }, completion: { _ in
        self.status.isHidden = true
        self.status.center = self.statusPosition

        self.showMessage(index: index+1)
      })
    }
    
    func resetForm() {
      UIView.transition(with: status, duration: 0.2, options: .transitionFlipFromTop, animations: {
        self.status.isHidden = true
        self.status.center = self.statusPosition
      }, completion: { _ in
        let tintColor = UIColor(red: 0.63, green: 0.84, blue: 0.35, alpha: 1.0)
        tintBackgroundColor(layer: self.loginButton.layer, toColor: tintColor)
        roundCorners(layer: self.loginButton.layer, toRadius: 10.0)
      })

      UIView.animate(withDuration: 0.2, delay: 0.0, options: [], animations: {
        self.spinner.center = CGPoint(x: -20.0, y: 16.0)
        self.spinner.alpha = 0.0
        self.loginButton.bounds.size.width -= 80.0
        self.loginButton.center.y -= 60.0
      }, completion: nil)
    }
    
    func animateCloud(layer: CALayer) {

      //1
      let cloudSpeed = 60.0 / Double(view.layer.frame.size.width)
      let duration: TimeInterval = Double(view.layer.frame.size.width - layer.frame.origin.x) * cloudSpeed

      //2
      let cloudMove = CABasicAnimation(keyPath: "position.x")
      cloudMove.duration = duration
      cloudMove.toValue = self.view.bounds.size.width + layer.bounds.width/2
      cloudMove.delegate = self
      cloudMove.fillMode = .forwards
      cloudMove.setValue("cloud", forKey: "name")
      cloudMove.setValue(layer, forKey: "layer")
      layer.add(cloudMove, forKey: nil)
    }

}

func delay(_ seconds: Double, completion: @escaping ()->Void) {
  DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: completion)
}

func tintBackgroundColor(layer: CALayer, toColor: UIColor) {
  let tint = CABasicAnimation(keyPath: "backgroundColor")
  tint.fromValue = layer.backgroundColor
  tint.toValue = toColor.cgColor
  tint.duration = 0.5
  layer.add(tint, forKey: nil)
  layer.backgroundColor = toColor.cgColor
}

func roundCorners(layer: CALayer, toRadius: CGFloat) {
  let round = CABasicAnimation(keyPath: "cornerRadius")
  round.fromValue = layer.cornerRadius
  round.toValue = toRadius
  round.duration = 0.5
  layer.add(round, forKey: nil)
  layer.cornerRadius = toRadius
}

class TextField: UITextField {

    let padding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 5)

    override open func textRect(forBounds bounds: CGRect) -> CGRect {
        return bounds.inset(by: padding)
    }

    override open func placeholderRect(forBounds bounds: CGRect) -> CGRect {
        return bounds.inset(by: padding)
    }

    override open func editingRect(forBounds bounds: CGRect) -> CGRect {
        return bounds.inset(by: padding)
    }
}

extension ViewController: CAAnimationDelegate {
  func animationDidStop(_ anim: CAAnimation,
                        finished flag: Bool) {
    print("animation did finish")

    guard let name = anim.value(forKey: "name") as? String else {
      return
    }

    if name == "form" {
      //form field found

      let layer = anim.value(forKey: "layer") as? CALayer
      anim.setValue(nil, forKey: "layer")

      let pulse = CABasicAnimation(keyPath: "transform.scale")
      pulse.fromValue = 1.25
      pulse.toValue = 1.0
      pulse.duration = 0.25
      layer?.add(pulse, forKey: nil)
    }

    if name == "cloud" {
      if let layer = anim.value(forKey: "layer") as? CALayer {
        anim.setValue(nil, forKey: "layer")

        layer.position.x = -layer.bounds.width/2
        delay(0.5) {
            self.animateCloud(layer: layer)
        }
      }
    }

  }
}
extension ViewController: UITextFieldDelegate {
  func textFieldDidBeginEditing(_ textField: UITextField) {
    guard let runningAnimations = info.layer.animationKeys() else {
      return
    }
    print(runningAnimations)
    info.layer.removeAnimation(forKey: "infoappear")
    info.layer.removeAnimation(forKey: "fadein")
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值