用UIPresentationController來寫一個簡潔漂亮的底部彈出控制元件

IsaacPan發表於2018-02-28

用UIPresentationController來寫一個簡潔漂亮的底部彈出控制元件

iOS App開發過程中,底部彈出框是一個非常常見的需求。如何寫一個漂亮的底部彈出框呢?方式有很多,直接新增一個自定義的View讓它動畫展示和隱藏都是一種非常簡單的操作,不過看起來似乎不那麼優雅,我們可以使用UIPresentationController來方便快捷地建立一個高定製化的底部彈出框。UIPresentationController的官方文件地址如下:

UIPresentationController: an object that manages the transition animations and the presentation of view controllers onscreen.

先上最終效果:

用UIPresentationController來寫一個簡潔漂亮的底部彈出控制元件

用UIPresentationController來寫一個簡潔漂亮的底部彈出控制元件

GitHub: github.com/IkeBanPC/Pr…

我們需要在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
}()
複製程式碼

重寫presentationTransitionWillBegindismissalTransitionWillBegindismissalTransitionDidEnd(_ 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)
    }
}
複製程式碼

之後在需要彈出的時候呼叫UIViewControllerpresentBottom(_ vc: PresentBottomVC.Type)方法就可以一句程式碼搞定啦:

self.presentBottom(FirstBottomVC.self)
複製程式碼

效果如下圖:

用UIPresentationController來寫一個簡潔漂亮的底部彈出控制元件

測試用的彈框寫好了,我們只要根據自己的需求去建立不同的PresentBottomVC的子類就可以方便快捷的實現各種各樣的底部彈出框啦。例項中的兩個效果可以參考GitHub原始碼

相關文章