1. 程式人生 > >自定義modal彈出樣式(swift)

自定義modal彈出樣式(swift)

0.假設vc是需要彈出的控制器(因為彈窗中有需要處理的事務,所以使用一個控制器管理)

1.彈出選單(使用modal將對應的控制器彈出)

presentViewController(vc, animated: true, completion: nil)

2.如果想要自己決定彈出方式,需要成為其代理

vc.transitioningDelegate = self
vc.modalPresentationStyle = .Custom

3.實現transitioningDelegate的代理方法

//該代理方法用於返回負責轉場的控制器物件
func presentationControllerForPresentedViewController(presented: UIViewController
, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController? { return PopoverPresentationController(presentedViewController: presented, presentingViewController: presenting) }
  //該代理方法用於告訴系統誰來負責控制器如何彈出
func animationControllerForPresentedController(presented: UIViewController
, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? { //當彈窗要彈出的時候就會呼叫該方法 return self }
    //該代理方法用於告訴系統誰來負責控制器如何消失
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
       //當彈窗要消失的時候就會呼叫該方法
return self }

4.自定義顯示和消失時彈窗展現方式

    // 該方法用於負責控制器如何彈出和如何消失
    // 只要是自定義轉場, 控制器彈出和消失都會呼叫該方法
    // 需要在該方法中告訴系統控制器如何彈出和如何消失
    // 注意點: 但凡告訴系統我們需要自己來控制控制器的彈出和消失
    // 也就是實現了UIViewControllerAnimatedTransitioning的方法之後, 那麼系統就不會再控制我們控制器的動畫了, 所有的操作都需要我們自己完成
    // 系統呼叫該方法時會傳遞一個transitionContext引數, 該引數中包含了我們所有需要的值
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {

        //在這裡自定義出現和消失時的動畫
    }

5.自定義蒙版

  • 注意點:
    所有被彈出的內容, 都是放在容器檢視上的

  • 說明:因為當彈窗出現的時候,背後的控制器是不能處理事件的,所以將繼承自 UIPresentationController的控制器(VC2)的containerView中新增一個UIBottom作為蒙版;

  • 因為UIBottom可以被監聽,在VC2中監聽蒙版,一旦點選,就是要消失彈窗;

  • 繼承自UIPresentationController的控制器負責管理自定義轉場動畫
    在VC2中的 containerViewWillLayoutSubviews方法中設定彈窗的尺寸和位置,在 presentationTransitionWillBegin方法中新增蒙版,因為彈出的內容,都是放在容器檢視中的,所以將蒙版新增到containerView中;在該方法中,containerView已經被建立;

6.設定顯示時動畫

因為顯示和消失動畫都是在第4步中的animateTransition方法中設定的,但是怎麼區分是出現還是消失彈窗呢?

在第3步中,

func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
     //當彈窗要彈出的時候就會呼叫該方法
     isPresent = true
        return self
}
func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
       //當彈窗要消失的時候就會呼叫該方法
     isPresent = false
        return self
 }

所以設定一個屬性isPresent,當是彈出時,在animationControllerForPresentedController方法中設定為true;
如果是小數,在animationControllerForDismissedController中設定為false;這樣在第4步中的animateTransition方法中設定動畫則根據屬性的值進行設定;

當isPresent = true 時:(抽取一個方法)

 /// 彈出動畫
    private func animatePresentedController(transitionContext: UIViewControllerContextTransitioning) {
        // 0.獲取動畫時長
        let duration = transitionDuration(transitionContext)
                // 1.拿到被彈出的控制器的View
        guard let toView = transitionContext.viewForKey(UITransitionContextToViewKey) else {
            return
        }
        // 2.將被彈出的控制器View新增到容器檢視上
        transitionContext.containerView()?.addSubview(toView)
        // 3.執行動畫
        // 3.1先將選單的View壓扁
        toView.transform = CGAffineTransformMakeScale(1.0, 0.0)
        toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
        // 3.2再清空選單壓扁的形變
        UIView.animateWithDuration(duration, animations: { () -> Void in
            toView.transform = CGAffineTransformIdentity
            }) { (_) -> Void in
                // 注意點: 但凡是自定義轉場, 一定要在自定義動畫完成之後告訴系統動畫已經完成了, 否則會出現一些未知異常
                transitionContext.completeTransition(true)
        }
    }

7.設定消失動畫

//動畫消失
    private func animateDismissedController(transitionContext: UIViewControllerContextTransitioning) {

        // 0.獲取動畫時長
        let duration = transitionDuration(transitionContext)

        // 1.拿到選單
        let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
        // 2.執行動畫
        UIView.animateWithDuration(duration, animations: { () -> Void in
            // 注意; 沒有動畫的原因是MakeScale接收的是CGFloat, 而CGFloat得值是不準確的
            fromView?.transform = CGAffineTransformMakeScale(1.0, 0.0001)
            }) { (_) -> Void in
             transitionContext.completeTransition(true)
        }
  }