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)
    }
}
相关文章
相关标签/搜索