swift項目第八天:自定義轉場動畫以及設置titleView的狀態

如圖效果:swift

一:Home控制器api

/*
 總結:1:設置登錄狀態下的導航欄的左右按鈕:1:在viewDidLoad裏用三目運算根據從父類繼承的islogin的登錄標識來判斷用戶是否登錄來顯示不一樣的界面。未登陸則顯示訪客界面,如果登錄則構建登錄界面 2:登錄界面須要:設置左右導航欄的按鈕:在viewDidLoad裏封裝設置登錄界面導航欄按鈕的方法,將具體代碼封裝在HomeViewController的extension中,定義方法屬性或是懶加載要考慮用private或是fileprivate來修飾,在當前class類中調用當前類的方法能夠省略掉self,給UIBarButtonItem寫一個分類,新建swiftFile,導入import UIKit框架,而後寫擴展:extension UIBarButtonItem {},在寫分類的時候既能夠提供class類方法也能夠提供構造方法,可是通常爲了外界調用方便都會提供構造函數方法,而且是便利構造函數。3:外界調用: navigationItem.leftBarButtonItem = UIBarButtonItem("navigationbar_friendattention", target: self, action: #selector(HomeViewController.clickLeftItem))其中 #selector(HomeViewController.clickLeftItem),這麼寫也能夠在監聽的方法中傳入參數,一樣監聽按鈕點擊的方法也封裝在當前類的extension中,在監聽按鈕點擊的方法中如果定義爲private或是fileprivate則用@objc 來修飾。4:一個按鈕能夠設置不一樣狀態下的圖片,可經過根據設置按鈕的不一樣狀態來顯示不一樣的按鈕圖片:1:一個按鈕設置不一樣的顯示狀態:
    titleViewBtn.isSelected = !titleViewBtn.isSelected 2:多個按鈕的切換,能夠設置currentBtn,設置按鈕的三部曲
 
 **/
import UIKit

class HomeViewController: RHBaseTableViewController {

     //MARK:-0:閉包懶加載屬性
    
    //1:懶加載titleView
    fileprivate lazy var titleViewButton :RHTitleViewButton  = {
        
        let titleViewBtn =  RHTitleViewButton()
        //1:設置標題
        titleViewBtn.setTitle("HELLO_SWIFT", for: .normal)
        //2:設置點擊的監聽方法
        titleViewBtn.addTarget(self, action: #selector(HomeViewController.clickTitleView), for: .touchUpInside)
        return titleViewBtn
    }()

    
    //2:懶加載Animator:經過閉包來設置titleView的狀態
    /*
     
    1: 1:注意:在閉包中若是使用當前對象的屬性或者調用方法,也須要加self
     兩個地方須要使用self : 1> 若是在一個函數中出現歧義 2> 在閉包中使用當前對象的屬性和方法也須要加self 2:在新建類定義閉包的時候,必須寫上新建類的類型,不然會報錯:
     fileprivate lazy var Animator :RHAnimator
    2: 閉包的循環引用:1:RHAnimator對閉包有一個強引用,其又爲home的屬性,因此home又對RHAnimator有一個強引用,在閉包中有引用了home的對象的屬性,因此對當前控制器對象home也會有一個強引用,相互之間引用從而形成循環引用 2:解決辦法:在參數列表前加[weak self]進行修飾,解決循環引用,[weak self]獲得的類型爲可選類型,如果可選類型能保證必定有值也就是不爲nil,則能夠進行強制解包,如果不能肯定1:則能夠用guard進行校驗 2:能夠在等號左邊用?,則表示有值則執行代碼,如果爲nil,則不執行後面的代碼:self?.titleViewButton.isSelected = presented
     */
    fileprivate lazy var Animator :RHAnimator  = RHAnimator  {[weak self] (presented) in
        
        self?.titleViewButton.isSelected = presented
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        
        //根據標識字段,構建訪客或是登錄視圖
        isLogin ? setupNavagationBar() : visitorView.rotationImage()

    }

}

//MARK:-3:設置首頁的導航欄
extension HomeViewController {
    
    //1:設置首頁的導航欄
    fileprivate func setupNavagationBar() {
        
    //1:設置首頁左側的導航欄按鈕.
    navigationItem.leftBarButtonItem = UIBarButtonItem("navigationbar_friendattention", target: self, action: #selector(HomeViewController.clickLeftItem))

    //2:設置首頁的右側的導航欄按鈕
    navigationItem.rightBarButtonItem = UIBarButtonItem("navigationbar_pop", target: self, action: #selector(HomeViewController.clickRightItem))
        

    //3:設置titleView
     navigationItem.titleView = titleViewButton//此處添加titleView的代碼會屢次調用layoutsubView
        
    }
    
}

//MARK:-4:監聽按鈕的點擊

extension HomeViewController {
    
    //1:點擊的是左側ietm
    @objc fileprivate func clickLeftItem(leftItem:UIBarButtonItem) {
        
        DLog(message: "點擊的是左側的按鈕------\(leftItem)")
    }
    
    //2:點擊的是右側的item
    @objc fileprivate func clickRightItem(rightItem:UIBarButtonItem) {
        
        DLog(message: "點擊的是右側的按鈕")
    }
    
    //3:點擊的是titleView的btn
    @objc fileprivate func clickTitleView (titleViewBtn:RHTitleViewButton){
        
        /*
         自定義轉場動畫:1:將彈出的view封裝爲一個控制器,通常將業務邏輯複雜的view都封裝爲一個控制器:建立彈出的控制器let popController = RHPopMenuViewController() 2:設置控制器的彈出樣式:自定義樣式 popController.modalPresentationStyle = .custom,枚舉值就用.custom
         3:設置轉場代理: popController.transitioningDelegate = Animator,1:將轉場代理設爲Animator,也就是將內部代理的方法封裝在Animator的內部,2:須要給Animator傳遞一個frame,3:彈出控制器: present(popController, animated: true, completion: nil),當調用當前類內部的屬性或是方法的時候,能夠省略調用self,可是在閉包內須要加上self
         注意:必須得是先去遵照協議,才能設置代理,不然系統會報錯
         */
        
        //1:建立彈出的控制器
        let popController = RHPopMenuViewController()
        
        //2:設置控制器的彈出樣式:自定義樣式
        popController.modalPresentationStyle = .custom
        
        //3:設置轉場代理
        popController.transitioningDelegate = Animator
        
        //4:設置彈出的frame
        Animator.presentFrame = CGRect(x: 100, y: 56, width: 180, height: 250)
        
        //5:彈出控制器
        present(popController, animated: true, completion: nil)
     
    }
    
}

二:RHPopMenuViewController:xib自定義:首先建立控制器RHPopMenuViewController定義xib,拖入UIImageView,設置背景圖片,會產生拉伸效果,在圖2處能夠處理其拉伸效果:設置豎直方向拉伸閉包

 三:封裝轉場動畫的代理方法app

import UIKit
/*
 總結:此類用於封裝轉場動畫的代理方法
 1:定義類的屬性:1:須要外界訪問的時候就不須要加關鍵字private,或是fileprivate,定義屬性的時候必須有初始化值或是定義成可選類型,定義的Bool標識默認取值爲false,定義成可選類型,肯定取值就強制解包,不肯定的時候能夠用guard進行校驗,或是在等號左側不進行校驗也不進行強制解包,用?,表示可選類型若爲nil,則不執行後面的代碼,若不爲nil則執行 2:定義閉包來進行回調,閉包的類型:(參數列表)->(返回值列表),以屬性定義閉包,要麼給閉包初始化賦值,要麼定義爲可選類型,要麼會報錯:var callBack : ((_ presented:Bool)->())?,如果想成爲內部參數,也就是外部調用的時候不顯示,則能夠用_+空格來表示。
 
 2:重寫init方法:1:注意:若是自定義了一個構造函數,可是沒有對默認構造函數init()進行重寫,那麼自定義的構造函數會覆蓋默認的init()構造函數,也就是在外部不能調用構造函數的方法。2:重寫init構造函數:防止自定義函數覆蓋掉默認的init()構造函數,
 override init() {
 super.init()
 }
 
 required init?(coder aDecoder: NSCoder) {
 fatalError("init(coder:) has not been implemented")
 }

 注意:當咱們重寫init或是重寫init(frame)構造函數的時候,必須重寫required init?(coder aDecoder: NSCoder) {
 fatalError("init(coder:) has not been implemented")
 }
 
 3:自定義構造函數:將閉包做爲參數傳進自定義的構造函數中,@escaping:聲明爲逃逸閉包,
 init(callBack : @escaping ((_ presented:Bool)->())) {
 
 self.callBack = callBack
 }
說白了:逃逸閉包和非逃逸閉包本質的區別就是:閉包做爲參數傳進函數體中的時候,若是當即調用閉包,就爲非逃逸閉包,若是不是當即調用閉包,將這個閉包保存在一個函數外部定義的變量中,在外部調用,逃逸出函數體,則爲逃逸閉包,
 
 
 
 
 */
class RHAnimator: NSObject {

     //MARK:-1:設置轉場代理的屬性
    
    //1:設置是否彈出的屬性標識
    fileprivate var present = false
    
    //2:提供frame屬性供外界去設置
    var presentFrame = CGRect.zero
    
    //3:定義閉包來回調,控制導航欄標題的顯示狀態(定義爲可選類型)
    var callBack : ((_ presented:Bool)->())?
    
    /*
     注意:若是自定義了一個構造函數,可是沒有對默認構造函數init()進行重寫,那麼自定義的構造函數會覆蓋默認的init()構造函數 */
    
    //4:重寫init構造函數:防止自定義函數覆蓋掉默認的init()構造函數
    override init() {
       super.init()
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    //5:自定義構造函數
    /*
     當一個閉包做爲參數傳到一個函數中,可是這個閉包在函數返回以後才被執行,咱們稱該閉包從函數中逃逸。當你定義接受閉包做爲參數的函數時,你能夠在參數名以前標註 @escaping,用來指明這個閉包是容許「逃逸」出這個函數的。
     
     一種能使閉包「逃逸」出函數的方法是,將這個閉包保存在一個函數外部定義的變量中。舉個例子,不少啓動異步操做的函數接受一個閉包參數做爲 completion handler。這類函數會在異步操做開始以後馬上返回,可是閉包直到異步操做結束後纔會被調用。在這種狀況下,閉包須要「逃逸」出函數,由於閉包須要在函數返回以後被調用。例如:
     
     var completionHandlers: [() -> Void] = []
     func someFunctionWithEscapingClosure(completionHandler: @escaping () -> Void) {
     completionHandlers.append(completionHandler)
     }
     
     如果可選類型在左側能夠直接給可選類型賦值,可是在以後使用可選類型的時候必定要進行解包
     */
    
    init(callBack : @escaping ((_ presented:Bool)->())) {
        
        self.callBack = callBack
    }
    
}

 //MARK:-2:轉場動畫的代理
extension RHAnimator:UIViewControllerTransitioningDelegate {
    
    //1:設置彈出控制器的
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        
        /*
         1:返回值爲UIPresentationController,因此自定義繼承系統的RHPresentationController,建立對象傳遞frame並返回
         2:UIPresentationController爲彈出的控制器,能夠對其進行設置
         
         */
        let presentVC = RHPresentationController(presentedViewController: presented, presenting: presenting)
        presentVC.presentedFrame = presentFrame
        return presentVC
    }

    /*
     1:在系統API中,代理方法有option的能夠選擇實現,沒有標註option的則爲在代理方法中必須實現的方法
     2:實現彈出動畫和消失動畫的方法:1:設置彈出的標識 ,爲了利用閉包回調標識,以便在home控制器中回調設置titleView的狀態 2:在利用閉包的時候,由於其爲可選類型,因此在使用的時候必定要進行解包,可選類型在左側的時候,能夠給可選類型直接賦值 3:返回值爲UIViewControllerAnimatedTransitioning, return self爲設置UIViewControllerAnimatedTransitioning的代理  3:實現UIViewControllerAnimatedTransitioning代理方法:
     
     */
    
    //2:設置的是彈出的動畫
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        //回調彈出標識來控制titleView的顯示狀態
        present = true
        self.callBack!(present)
        
        return self
        
    }
    
   //3:設置的是消失的動畫
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
         //回調彈出標識來控制titleView的顯示狀態
         present = false
         self.callBack!(present)
         return self
    }

}

extension RHAnimator:UIViewControllerAnimatedTransitioning {
   
    
    /**
     
     1:遵照UIViewControllerAnimatedTransitioning代理,實現代理方法來設置動畫的方法:1:設置動畫彈出的時間 2:經過轉場上下文transitionContext獲取轉場彈出或是消失的view,根據是否彈出的標識來動畫控制view的彈出和消失:定義兩個方法,根據標識來控制消失仍是彈起的動畫。
     
     2: 獲取`轉場的上下文`:能夠經過轉場上下文獲取彈出的View和消失的View
     UITransitionContextFromViewKey : 獲取消失的View
     UITransitionContextToViewKey : 獲取彈出的View
     
    1.獲取彈出的View:  1:let presentView = transitionContext.view(forKey: UITransitionContextViewKey.to)! 獲取的presentView是一個可選類型,能保證其必定有值,因此能夠進行強制解包 2:添加到containerView上transitionContext.containerView.addSubview(presentView)
     
     執行動畫:設置transform的縮放效果,x方向不須要縮放,y方向縮放值爲0,錨點anchorPoint,在其中心點的位置,因此要想設置其從上往下拉的效果就設置錨點y的值爲0,不要使用傳遞函數體的參數的時候可使用(_)來表示,在完成動畫以後要設置必須告訴轉場上下文你已經完成動畫,  transitionContext.completeTransition(true)

     presentView.transform = CGAffineTransform(scaleX: 1.0, y: 0.0)
     presentView.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
     UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
     
     presentView.transform = CGAffineTransform.identity
     
     }) { (_) in
     
     // 必須告訴轉場上下文你已經完成動畫
     transitionContext.completeTransition(true)
     }
     
     }
     
     */
    //1:設置動畫彈出的時間
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        
        return 0.5
    }
    
    //2:經過轉場上下文獲取轉場彈出或是消失的view,根據是否彈出的標識來動畫控制view的彈出和消失
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning){
        
        present ? animateForpresent(transitionContext) : animateFordissmiss(transitionContext)
    }
    
    
    /*
     獲取`轉場的上下文`:能夠經過轉場上下文獲取彈出的View和消失的View
     UITransitionContextFromViewKey : 獲取消失的View
     UITransitionContextToViewKey : 獲取彈出的View

     */
    
    //3:動畫彈出view
    fileprivate  func animateForpresent (_ transitionContext:UIViewControllerContextTransitioning)  {
        
        // 1.獲取彈出的View
        let presentView = transitionContext.view(forKey: UITransitionContextViewKey.to)!
        
       // 2.將彈出的View添加到containerView中
       transitionContext.containerView.addSubview(presentView)
        
       //3:執行動畫
        presentView.transform = CGAffineTransform(scaleX: 1.0, y: 0.0)
        presentView.layer.anchorPoint = CGPoint(x: 0.5, y: 0)
        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { 
            
            presentView.transform = CGAffineTransform.identity
            
            }) { (_) in

           // 必須告訴轉場上下文你已經完成動畫
           transitionContext.completeTransition(true)
        }
        
    }
    
    
    //4:動畫消失view
    fileprivate func animateFordissmiss (_ transitionContext:UIViewControllerContextTransitioning){
        
        // 1.獲取消失的View
        let dissmissView = transitionContext.view(forKey: UITransitionContextViewKey.from)
        
        //3:執行動畫
        UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
            
            dissmissView?.transform = CGAffineTransform.identity
            
        }) { (_) in
            dissmissView?.removeFromSuperview()
            // 必須告訴轉場上下文你已經完成動畫
            transitionContext.completeTransition(true)
        }

    }
    
}

 

四: 封裝彈出的控制器框架

/*
 總結:1:此類是用於設置彈出view的frame和添加蒙版 2:重寫UIPresentationController的containerViewWillLayoutSubviews方法,override來重寫,在此方法內不要忘記調用super 2:重寫此方法:1:設置彈出view的frame:presentedView?.frame = presentedFrame,由於presentedView爲彈出view,爲可選類型,肯定有值能夠強制拆包,或是直接?使用,不爲nil則執行,爲nil則不執行,可是在以後的代碼中使用須要進行解包,能夠給可選類型進行直接賦值。可是不能將可選類型直接賦值給其餘的變量
   
 2:添加蒙版:1:在containerView上添加蒙版,containerView?.insertSubview(cover, at: 0),2:設置顏色: cover.backgroundColor = UIColor(white: 0.8, alpha: 0.9),white爲1滿色的時候爲白色,0爲黑色,alpha透明度,爲0徹底透明 3:設置frame: cover.frame = (containerView?.bounds)!也能夠:containerView!.bounds
    
 3:添加手勢:let tap = UITapGestureRecognizer(target: self, action: #selector(RHPresentationController.clickBg))
      cover.addGestureRecognizer(tap),手勢監聽的方法也寫在extension中,顯示移除彈出的view,而後是dissmiss掉控制器
 */
import UIKit

class RHPresentationController: UIPresentationController {
    
     //MARK:-1:設置屬性
    
    //1:彈出view的尺寸
     var presentedFrame = CGRect.zero
    
    //2:懶加載遮蓋的view
    fileprivate lazy var cover = UIView()
    
    //3:重寫佈局函數設置彈出view的frame:不要忘記調用super
    override func containerViewWillLayoutSubviews() {
        
      super.containerViewWillLayoutSubviews()
        //1:設置彈出view的frame
       presentedView?.frame = presentedFrame
        
        //2:添加蒙版
       setUpCover()
        
    }
}

extension RHPresentationController {
    
    //1:添加蒙版
    fileprivate func setUpCover() {
        
    //1:添加到containnerView中
      containerView?.insertSubview(cover, at: 0)
      cover.backgroundColor = UIColor(white: 0.8, alpha: 0.2)
      cover.frame = (containerView?.bounds)!//containerView!.bounds
        
    //2:添加手勢
      let tap = UITapGestureRecognizer(target: self, action: #selector(RHPresentationController.clickBg))
        
      cover.addGestureRecognizer(tap)
    }
    
}

extension RHPresentationController {
    
    @objc fileprivate func clickBg() {
        
       //1:移除遮蓋
       cover.removeFromSuperview()
        
      //2:dissmiss掉控制器
      presentedViewController.dismiss(animated: true, completion: nil)
    }
}
相關文章
相關標籤/搜索