[Cmoney 菁英软件工程师战斗营] IOS APP 菜鸟开发笔记(7)----自定义弹出视窗

前言

因为UI和UX方面的需求,这几天上网搜寻了如何自定义下一页的弹出大小,弹出位置和动画,发现有蛮多种方法都能达到成,於是选了个来实作。因为目前的进度压力比较大,所以很多地方没有写得有弹性和封装好,再请见谅了。

首先我们来设计一个由下往上弹出的页面,自定义的部分为弹出後的高度,这边用protocol来让其他类修改。

public protocol PresentBottomVCProtocol {
    var controllerHeight: CGFloat {get}
}

再来要写一个继承 UIPresentationController 的类,里面可以添加动画和遮罩效果。

public class PresentBottom:UIPresentationController {

    ///  黑色遮罩
    lazy var blackView: UIView = {
        let view = UIView()
        if let frame = self.containerView?.bounds {
            view.frame = frame
        }
        view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
        let gesture = UITapGestureRecognizer(target: self, action: #selector(sendHideNotification))
        view.addGestureRecognizer(gesture)
        return view
    }()
    
    /// 调整高度
    public var controllerHeight:CGFloat
    
    public override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
        //get height from an objec of PresentBottomVC class
        if case let vc as PresentBottomVC = presentedViewController {
            controllerHeight = vc.controllerHeight
        } else {
            controllerHeight = UIScreen.main.bounds.width
        }
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
    }
    
//    /// 在弹窗即将出现时把遮罩添加到containerView,并通过动画将遮罩的alpha设置为1
    public override func presentationTransitionWillBegin() {
        blackView.alpha = 1
        containerView?.addSubview(blackView)

    }

    ///  在弹窗即将消失时做的事
    public override func dismissalTransitionWillBegin() {
        
    }

    /// 在弹框消失之后将遮罩从containerView上移除
    public override func dismissalTransitionDidEnd(_ completed: Bool) {
        if completed {
            blackView.removeFromSuperview()
        }
    }
    
    ///   决定了弹出框的frame, 它决定了弹出框在屏幕中的位置,由于我们是底部弹出框,我们设定一个弹出框的高度controllerHeight,即可得出弹出框的frame
    public override var frameOfPresentedViewInContainerView: CGRect {
        return CGRect(x: 0, y: UIScreen.main.bounds.height-controllerHeight, width: UIScreen.main.bounds.width, height: controllerHeight)
    }
    
    
extension UIViewController: UIViewControllerTransitioningDelegate {
    

    /// - Parameter vc: class name of bottom view  
    public func presentBottom(_ vc: PresentBottomVC ) {
        vc.modalPresentationStyle = .custom
        vc.transitioningDelegate = self
        vc.modalTransitionStyle = UIModalTransitionStyle.coverVertical //由下跳转的动画
        self.present(vc, animated: true, completion: nil)
    }
    
    /// - Parameter vc: class name of bottom view
    public func presentMiddle(_ vc: PresentMiddleVC ) {
        vc.modalPresentationStyle = .custom
        vc.transitioningDelegate = self
        vc.modalTransitionStyle = UIModalTransitionStyle.crossDissolve //直接跳转的动画
        self.present(vc, animated: true, completion: nil)
    }
    
    
    
    // function refers to UIViewControllerTransitioningDelegate
    public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        
        if  case let vc as PresentBottomVC = presented {
            let present = PresentBottom(presentedViewController: vc, presenting: presenting)
            return present
        }else if case let vc2 as PresentMiddleVC = presented{
            let present = PresentMiddle(presentedViewController: vc2, presenting: presenting)
            return present
        }

       return nil
    }
}

接下来设计一个基底类实现刚才写的protocol

public let PresentBottomHideKey = "ShouldHidePresentBottom"

public class PresentBottomVC: UIViewController, PresentBottomVCProtocol {
    public var controllerHeight: CGFloat {
        return 0
    }
    
    public override func viewDidLoad() {
        super.viewDidLoad()
        NotificationCenter.default.addObserver(self, selector: #selector(presentBottomShouldHide), name: NSNotification.Name(PresentBottomHideKey), object: nil)
    }
    
    public override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        NotificationCenter.default.removeObserver(self, name: NSNotification.Name(PresentBottomHideKey), object: nil)
    }
    
    @objc func presentBottomShouldHide() {
        self.dismiss(animated: true, completion: nil)
    }
    
}

以後我们只要让新的 Controller 继承 PresentBottomVC 并覆写controllerHeight後,就能愉快地设定弹出後的高度了!

class TestViewController:  PresentBottomVC {

    override var controllerHeight: CGFloat {
        return screenSize.height*4/7
    }

如果要设定弹出在画面中间,类似 AlertView 的弹出视窗,也可以照以上流程设计。在上方的UIViewControllerTransitioningDelegate 那可以设定弹出的动画效果,以及外界该如何呼叫我们的弹出视窗。


<<:  我的 QA 道路之你怎麽又被炒了

>>:  2020网页切版直播班-心得

前端工程师也能开发全端网页:挑战 30 天用 React 加上 Firebase 打造社群网站|Day20 会员选单

连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...

Day6中秋节最後还是要吃肉肉阿-欧式地中海风马铃薯柠檬炖鸡

中秋假期最後一天,吃了美式、韩式及日式烧肉,吃了这麽多天大鱼大肉可能有点腻,最後一天想来点清爽的肉肉...

[Day15] 透过绿野仙踪实验进行可用性测试

在昨天的文章中,快速而简洁地向各位介绍语音对话设计中「测试与迭代」的相关名词 在今天的文章中我们将...

图的储存结构 - 相邻串列 - DAY 21

前言 昨天相邻矩阵权重图,没有连结到的点 从 0 修改为 ∞ ,可以想像没有连结到的节点为不可能连结...

Day5 开始Vue前要具备的基础

在开始前我们先来介绍一些看似基础但却很重要的工具吧。 var、let和const var、let、c...