iOS動畫:ViewController過渡動畫之Presentation Controller和Orientation動畫(11)

Presentation Controller

我們來改變系統自帶的present動畫,主要用到UIViewControllerTransitioningDelegate的代理方法animationController(forPresented:presenting:source:),該代理方法需要返回一個UIViewControllerAnimatedTransitioning的可選對象,當返回nil時,UIKit將使用內置系統默認動畫,當不爲nil時,將使用你自己自定義的動畫效果。

現在有這樣一個場景:
image_1
我們在點擊底下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容器中移除
	}

這樣就實現了淡入的轉場動畫

image_2

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

運行效果
image_3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章