用UIPresentationController来写一个简洁漂亮的底部弹出控件

百家 作者:iOS开发 2018-03-26 11:42:39

点击上方“iOS开发”,选择“置顶公众号”

关键时刻,第一时间送达!


iOS App开发过程中,底部弹出框是一个非常常见的需求。如何写一个漂亮的底部弹出框呢?方式有很多,直接添加一个自定义的View让它动画展示和隐藏都是一种非常简单的操作,不过看起来似乎不那么优雅,我们可以使用UIPresentationController来方便快捷地创建一个高定制化的底部弹出框。UIPresentationController的官方文档地址如下:


UIPresentationController(https://developer.apple.com/documentation/uikit/uipresentationcontroller) : an object that manages the transition animations and the presentation of view controllers onscreen.


先上最终效果:




GitHub: https://github.com/IkeBanPC/PresentBottom

我们需要在iOS8及以上的系统中使用UIPresentationController,使用时需要新建一个类继承UIPresentationController并重写以下几个方法和属性:


//决定了弹出框的frame

override var frameOfPresentedViewInContainerView


//重写此方法可以在弹框即将显示时执行所需要的操作

override func presentationTransitionWillBegin()


//重写此方法可以在弹框显示完毕时执行所需要的操作

override func presentationTransitionDidEnd(_ completed: Bool)


//重写此方法可以在弹框即将消失时执行所需要的操作

override func dismissalTransitionWillBegin()


//重写此方法可以在弹框消失之后执行所需要的操作

override func dismissalTransitionDidEnd(_ completed: Bool)


重写初始化方法:


override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {

    super.init(presentedViewController:presentedViewController,presenting: presentingViewController)

}


在大多数时候,我们希望底部弹出框出现时,先前的显示区域能够灰暗一些,来强调弹出框的显示区域是用户当前操作的首要区域。因此,我们给这个自定义的类添加一个遮罩:


lazy var blackView: UIView = {

    let view = UIView()

    if let frame = self.containerView?.bounds {

        view.frame = frame

    }

    view.backgroundColor = UIColor.black.withAlphaComponent(0.5)

    return view

}()


重写presentationTransitionWillBegin、dismissalTransitionWillBegin和dismissalTransitionDidEnd(_ completed: Bool)方法。在弹窗即将出现时把遮罩添加到containerView,并通过动画将遮罩的alpha设置为1;在弹窗即将消失时通过动画将遮罩的alpha设置为0;在弹框消失之后将遮罩从containerView上移除:


override func presentationTransitionWillBegin() {

    blackView.alpha = 0

    containerView?.addSubview(blackLayerView)

    UIView.animate(withDuration: 0.5) {

        self.blackView.alpha = 1

    }

}


override func dismissalTransitionWillBegin() {

    UIView.animate(withDuration: 0.5) {

        self.blackView.alpha = 0

    }

}


override func dismissalTransitionDidEnd(_ completed: Bool) {

    if completed {

        blackView.removeFromSuperview()

    }

}


接下来,我们重写frameOfPresentedViewInContainerView这个属性。它决定了弹出框在屏幕中的位置,由于我们是底部弹出框,我们设定一个弹出框的高度controllerHeight,即可得出弹出框的frame:


override var frameOfPresentedViewInContainerView: CGRect {

    return CGRect(x: 0, y: UIScreen.main.bounds.height-controllerHeight, width: UIScreen.main.bounds.width, height: controllerHeight)

}


为了便于我们创建各种各样的底部弹出框,我们创建一个基类PresentBottomVC继承自UIViewController,并添加一个属性controllerHeight用于得到弹出框的高度:


public class PresentBottomVC: UIViewController {

    public var controllerHeight: CGFloat? {

        get {

            return self.controllerHeight

        }

    }

}


之后,我们就可以新建继承自PresentBottomVC并重写controllerHeight属性的类来实现定制化底部弹出框。为了方便调用弹出方法,我们给UIViewController添加一个Extension,并实现UIViewControllerTransitioningDelegate协议:


public func presentBottom(_ vc: PresentBottomVC.Type) {

    let controller = vc.init()

    controller.modalPresentationStyle = .custom

    controller.transitioningDelegate = self

    self.present(controller, animated: true, completion: nil)

}

public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {

    let present = PresentBottom(presentedViewController: presented, presenting: presenting)

    return present

}


可以看到,所有继承自PresentBottomVC的ViewController都可以通过该方法来从另一个ViewController的底部弹出。例如,我们新建一个类FirstBottomVC,重写controllerHeight并设为200,在页面中添加一个关闭按钮:


final class FirstBottomVC: PresentBottomVC {

    lazy var closeButton:UIButton = {

        let button = UIButton(frame: CGRect(x: 15, y: 30, width: 80, height: 30))

        button.setTitle("Close", for: .normal)

        button.setTitleColor(.black, for: .normal)

        button.addTarget(self, action: #selector(closeButtonClicked), for: .touchUpInside)

        return button

    }()

    override var controllerHeight: CGFloat? {

        return 200

    }

    override func viewDidLoad() {

        super.viewDidLoad()

        view.backgroundColor = .cyan

        view.addSubview(closeButton)

    }

    @objc func closeButtonClicked() {

        self.dismiss(animated: true, completion: nil)

    }

}


之后在需要弹出的时候调用UIViewController的presentBottom(_ vc: PresentBottomVC.Type)方法就可以一句代码搞定啦:


self.presentBottom(FirstBottomVC.self)


效果如下图:



测试用的弹框写好了,我们只要根据自己的需求去创建不同的PresentBottomVC的子类就可以方便快捷的实现各种各样的底部弹出框啦。实例中的两个效果可以参考GitHub源码(https://github.com/IkeBanPC/PresentBottom)




  • 作者:IsaacPan

  • 链接:https://juejin.im/post/5a9651d25188257a5911f666

  • iOS开发整理发布,转载请联系作者授权

【点击成为Java大神】

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接