如圖效果: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) } }