iOS动画:UIViewPropertyAnimator动画之ViewController Present过渡(17)

22 篇文章 0 订阅
21 篇文章 0 订阅

静态ViewController过渡动画

首先实现一个present的自定义动画。
创建PresentTransition,实现UIViewControllerAnimatedTransitioning协议:

import UIKit

class PresentTransition: NSObject, UIViewControllerAnimatedTransitioning {
  func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.75
  }
  func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    transitionAnimator(using: transitionContext).startAnimation()
  }
  func transitionAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    let duration = transitionDuration(using: transitionContext)
    let container = transitionContext.containerView
    let to = transitionContext.view(forKey: .to)!
    
    container.addSubview(to)
    to.transform = CGAffineTransform(scaleX: 1.33, y: 1.33)
    .concatenating(CGAffineTransform(translationX: 0.0, y: 200))
    to.alpha = 0
    
    let animator = UIViewPropertyAnimator(duration: duration, curve: .easeOut)
    animator.addAnimations({
      to.transform = CGAffineTransform(translationX: 0.0, y: 100)
    }, delayFactor: 0.15)
    
    animator.addAnimations({
      to.alpha = 1.0
    }, delayFactor: 0.5)
    animator.addCompletion { (_) in
      transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
    }
    return animator
  }
}

制作present动画看上去很简单,前面章节已经介绍过了,这里不做赘述。
将动画运用于UIViewControllerTransitioningDelegate的代理方法:

extension LockScreenViewController: UIViewControllerTransitioningDelegate {
  func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    return presentTransition
  }
}

present到编辑页面:

  @IBAction func presentSettings(_ sender: Any? = nil) {
    //present the view controller
    settingsController = storyboard?.instantiateViewController(withIdentifier: "SettingsViewController") as! SettingsViewController
    settingsController.transitioningDelegate = self
    present(settingsController, animated: true, completion: nil)
  }

当present到编辑页面时,因为编辑页面背景色是透明的,所以看上去效果是这样的:
image_1
为PresentTransition添加动画属性,让外部可以注入自己的动画

  var auxAnimations: (() -> Void)?
  var auxAnimationsCancel: (()->Void)?

在animator返回前加入:

    if let auxAnimations = auxAnimations {
      animator.addAnimations(auxAnimations)
    }

在LockScreenViewController的presentSettings方法中添加动画:

    presentTransition.auxAnimations = blurAnimations(true)

运行效果:
image_2
当用户点击cancel时dismiss编辑界面:

    settingsController.didDismiss = { [unowned self] in
      self.toggleBlur(false)
    }

ViewController过渡交互

和UINavigationController动画交互类似,所使用的类是一样的,都是UIPercentDrivenInteractiveTransition。
替换PresentTrasition中的类继承:

class PresentTransition: UIPercentDrivenInteractiveTransition, UIViewControllerAnimatedTransitioning

UIPercentDrivenInteractiveTransition的三个主要方法(update,cancel,finish)前面已经介绍过了。它还有一些其他的属性及方法:
timingCurve:跟前面章节一样,提供交互结束后的动画曲线
wantsInteractiveStart:默认值为true,如果设置为false,你将不能进行交互,你可以通过pause()后继续进行交互。
paused():调用这个方法将暂停非交互性的过渡动画,并进入交互模式。
添加新的方法:

  func interruptibleAnimator(using transitionContext: UIViewControllerContextTransitioning) -> UIViewImplicitlyAnimating {
    return transitionAnimator(using: transitionContext)
  }

这个方法是UIViewControllerAnimatedTransitioning协议里面的,它允许你提供一个中断的动画。
你的过渡动画现在有两种不同的形式:
1.非交互性的动画,UIKit将回调animateTransition(using:)代理方法用于实现动画。
2.交互性的动画,UIKit将回调interruptibleAnimator(using:)代理方法用于实现动画。
流程图为:
image_3
切换到LockScreenViewController,添加UIViewControllerTransitioningDelegate的代理方法:

  func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
    return presentTransition
  }

添加属性:

  var isDragging = false
  var isPresentingSettings = false

当用户下拉table时,设置isDragging为true,当下拉到足够距离设置isPresentingSettings为true,实现scroll代理方法:

extension LockScreenViewController: UIScrollViewDelegate {
  func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
    isDragging = true
  }
  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    guard isDragging else {
      return
    }
    if !isPresentingSettings && scrollView.contentOffset.y < -30 {
      isPresentingSettings = true
      presentTransition.wantsInteractiveStart = true
      presentSettings()
      return
    }
  }
}

在scrollViewDidScroll(_:)中添加交互代码:

    if isPresentingSettings {
      let progress = max(0.0, min(1.0, ((-scrollView.contentOffset.y) - 30) / 90.0))
      presentTransition.update(progress)
    }

当用户慢慢下拉时,table慢慢变模糊,运行效果:
image_4
当完成和取消交互式过渡时,你需要做一些处理:

  func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let progress = max(0.0, min(1.0, ((-scrollView.contentOffset.y) - 30) / 90.0))
    if progress > 0.5 {
      presentTransition.finish()
    }else {
      presentTransition.cancel()
    }
    isPresentingSettings = false
    isDragging = false
  }

修改PresentTransition中动画完成时的block:

    animator.addCompletion { position in
      switch position {
      case .end:
        transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
      default:
        transitionContext.completeTransition(false)
      }
    }

只有当动画在end位置结束时,才算present完成。其它都是未完成。
在iOS10下反复拉列表,过渡开始后立马取消会出现问题,iOS11下已经修复了这个问题:
image_5
这跟visual effect view有关,当这种view放在block动画里面,当动画反转或者取消时似乎不会被移除掉,所以看起来一团糟,所以我们需要手动将其移除,那就要用到我们前面定义的auxAnimationsCancel。
找到animator.addCompletion,添加代码到default:

self.auxAnimationsCancel?()

当动画没有完成时,它将调用,所以我们在LockScreenViewController中将它移除

    presentTransition.auxAnimationsCancel = blurAnimations(false)

这就解决啦。
新的问题又出现了,因为wantsInteractiveStart默认值是true,所以点击edit时不会调用animateTransition(using:)中的方法,非交互式动画将不会开始,所以在点击edit时修改wantsInteractiveStart为false

	self.presentTransition.wantsInteractiveStart = false

现在我们来考虑一种情况,在用户点击“Edit”时,动画期间当用户再次点击屏幕,我们需要暂停转换,这就需要考虑transition在交互式和非交互式之间切换。
切换到PresentTranstion.swift,你不仅需要分别处理交互式模式和非交互式模式,而且还要处理它们之间的切换,添加属性来保存动画上下文:

  var context: UIViewControllerContextTransitioning?
  var animator: UIViewPropertyAnimator?

在transitionAnimator(using:)里面添加:

    self.animator = animator
    self.context = transitionContext

动画完成时设置为nil

    animator.addCompletion { [unowned self] _ in
      self.animator = nil
      self.context = nil
    }

现在你可以添加一个方法来中断transition

  func interruptTransition() {
    guard let context = context else { return }
    context.pauseInteractiveTransition()//暂停animator
    pause()//将transition切换到交互模式
  }

为了允许在非交互模式下能点击,你需要设置animator响应手势

    animator.isUserInteractionEnabled = true

当进入交互模式后,允许用户取消或者完成transition,在LockScreenViewController中添加属性

    var touchesStartPointY: CGFloat?

点击屏幕时中断transition

  override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard presentTransition.wantsInteractiveStart == false, presentTransition.animator != nil else {
      return
    }
    touchesStartPointY = touches.first!.location(in: view).y
    presentTransition.interruptTransition()
  }

当用户进行拖动时,修改touchesMoved方法:

  override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    guard let startY = touchesStartPointY else { return }
    let currentPoint = touches.first!.location(in: view).y
    if currentPoint < startY - 40 {
      touchesStartPointY = nil
      presentTransition.animator?.addCompletion({ (_) in
        self.blurView.effect = nil
      })
      presentTransition.cancel()
    }else if currentPoint > startY + 40 {
      touchesStartPointY = nil
      presentTransition.finish()
    }
  }

运行效果
image_6

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值