Presentation Controller

我们来改变系统自带的present动画,主要用到UIViewControllerTransitioningDelegate的代理方法animationController(forPresented:presenting:source:),该代理方法需要返回一个UIViewControllerAnimatedTransitioning的可选对象,当返回nil时,UIKit将使用内置系统默认动画,当不为nil时,将使用你自己自定义的动画效果。

现在有这样一个场景:

仿iOS过渡动画修改器 ios11改ios10过渡动画_转场动画


我们在点击底下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容器中移除
	}

这样就实现了淡入的转场动画

仿iOS过渡动画修改器 ios11改ios10过渡动画_转场动画_02

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

运行效果

仿iOS过渡动画修改器 ios11改ios10过渡动画_sed_03