Presentation Controller
我们来改变系统自带的present动画,主要用到UIViewControllerTransitioningDelegate的代理方法animationController(forPresented:presenting:source:),该代理方法需要返回一个UIViewControllerAnimatedTransitioning的可选对象,当返回nil时,UIKit将使用内置系统默认动画,当不为nil时,将使用你自己自定义的动画效果。
现在有这样一个场景:
我们在点击底下image时,需要将图片scale到一个新的界面,而不是采用系统默认的present动画从屏幕底部开始覆盖。
present转场动画
1.我们创建一个动画构造器,命名为PopAnimator.swift
class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
}
}
2.在FirstViewController里面实现UIViewControllerTransitioningDelegate的代理方法
class ViewController: UIViewController {
let transition = PopAnimator()
...
}
extension ViewController: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return transition
}
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return nil
}
}
3.设置SecondViewController的过渡代理
secondVC.transitioningDelegate = self
上面做了一些基本的初始化,现在我们来实现我们的动画构造器PopAnimator.swift。
首先为PopAnimator添加三个属性:
let duration = 1.0//动画时间
var presenting = true//用来判断是present还是dismiss
var originFrame = CGRect.zero//用来存储image的原始位置
替换transitionDuration()的返回值为
return duration
现在我们来为animateTransition添加动画,它有一个UIViewControllerContextTransitioning类型的参数。在这之前,你需要了解动画上下文。
当present转场动画开始时,firstView将被添加到转场容器(container)里面,secondView将被创建但是屏幕上不可见,因此我们主要做的就是为容器add一个secondView并赋予进入的动画,为firstView添加移除的动画。默认情况下,firstView并不需要我们remove,动画完成后会自动从container容器中移除。
我们来创建一个简单的淡入动画来测试一下。
添加以下代码到animateTransition()
let containerView = transitionContext.containerView//获得动画的容器
let toView = transitionContext.view(forKey: .to)!//取得secondView
//let toViewController = transitionContext.viewController(forKey: .to)!//取secondViewController
containerView.addSubview(toView)//添加secondView到容器里面
toView.alpha = 0.0//设置secondView初始透明度为0
UIView.animate(withDuration: duration, animations: {
toView.alpha = 1.0
}) { (_) in
transitionContext.completeTransition(true)//告诉UIKit转场动画已经完成,转场完成时,firstView会自动从container容器中移除
}
这样就实现了淡入的转场动画
pop转场动画
替换animateTransition()里面的代码:
let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let secondView = presenting ? toView : transitionContext.view(forKey: .from)!
当present时,secondView是toView,当pop时,secondView变成了fromView。
设置secondView动画前坐标及放大倍数
let initialFrame = presenting ? originFrame : secondView.frame
let finalFrame = presenting ? secondView.frame : originFrame
let xScaleFactor = presenting ? initialFrame.width / finalFrame.width : finalFrame.width / initialFrame.width
let yScaleFactor = presenting ? initialFrame.height / finalFrame.height : finalFrame.height / initialFrame.height
let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor)
if presenting {
secondView.transform = scaleTransform
secondView.center = CGPoint(x: initialFrame.midX, y: initialFrame.midY)
secondView.clipsToBounds = true
}
添加动画
containerView.addSubview(toView)
containerView.bringSubview(toFront: secondView)
UIView.animate(withDuration: duration, delay: 0.0, usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0, animations: {
secondView.transform = self.presenting ? CGAffineTransform.identity : scaleTransform
secondView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
secondView.layer.cornerRadius = self.presenting ? 0.0 : 20.0/xScaleFactor
}) { (_) in
transitionContext.completeTransition(true)
}
在FirstViewController中的animationController(forPresented:)方法里面设置PopAnimator的初始值,present动画就做好了。
transition.originFrame = selectedImage!.superview!.convert(selectedImage!.frame, to: nil)
transition.presenting = true
selectedImage!.isHidden = true
在animationController(forDismissed:)里面设置PopAnimator初始值,dismiss动画就做好了。
transition.presenting = false
return transition
在dismiss动画完成后,需要将selectImage取消隐藏
为PopAnimator.swift添加一个block
var dismissCompletion: (()->Void)?
在completeTransition()之前调用
if !self.presenting {
self.dismissCompletion?()
}
在FirstViewController的viewDidLoad里面添加回调显示selectImage
transition.dismissCompletion = {
self.selectedImage!.isHidden = false
}
Orientation动画
设备方向改变时,我们在FirstViewController里重新设置原Image的位置及尺寸
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(alongsideTransition: { (context) in
self.bgImage.alpha = (size.width > size.height) ? 0.25 : 0.55
self.positionListItems()//重新布局
}, completion: nil)
}
运行效果