底部弹出框在项目中很常见,之前对此的理解很肤浅,不太明白如何更好的实现自定义的页面。刚好在 GitHub 看到有一中很简单的实现方式,是利用 UIPresentationController 和继承的方式实现。学习一下,自己再通过协议的方式实现下,加深自己的理解。
实现效果

实现 UIPresentationController
关于 UIPresentationController
的描述,官网的说法是
An object that manages the transition animations and the presentation of view controllers onscreen.
简单点说就是管理两个 Controller 之间的转场动画。
所以可以通过重写这个类来自定义想要的转场动画。
首先创建一个类来继承 UIPresentationController
,并重写一些必要的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| class HUPresentController: UIPresentationController { true var controllerHeight: CGFloat = 0 override var frameOfPresentedViewInContainerView: CGRect { return CGRect(x: 0, y: UIScreen.main.bounds.height - controllerHeight , width: UIScreen.main.bounds.width, height: controllerHeight) } override func presentationTransitionWillBegin() { blackView.alpha = 0 containerView?.addSubview(blackView) UIView.animate(withDuration: 0.3) { self.blackView.alpha = 1 } } override func presentationTransitionDidEnd(_ completed: Bool) { } override func dismissalTransitionWillBegin() { UIView.animate(withDuration: 0.5) { self.blackView.alpha = 0 } } override func dismissalTransitionDidEnd(_ completed: Bool) { if completed { blackView.removeFromSuperview() } } 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 }()
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) { if let vc = presentedViewController as? PresentProtocol { controllerHeight = vc.controllerHeight } super.init(presentedViewController:presentedViewController,presenting: presentingViewController) } }
|
协议 && UIViewController 扩展
原来的项目是通过继承的方式来实现的,但是在 Swift 中并不提倡使用继承,所以我们改用协议的方式来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| protocol PresentProtocol { var controllerHeight: CGFloat { get } } extension PresentProtocol where Self: UIViewController {}
extension UIViewController: UIViewControllerTransitioningDelegate { func presentBottom(_ vc: UIViewController) { vc.modalPresentationStyle = .custom vc.transitioningDelegate = self self.present(vc, animated: true, completion: nil) } public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? { let present = HUPresentController(presentedViewController: presented, presenting: presenting) return present } }
|
实现自定义 Controller
准备工作到了最后一步就是实现自定义的 Controller 了,确保 Controller 遵循刚才定义的协议 PresentProtocol
,并实现协议属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| class HUTestVC: UIViewController, PresentProtocol { var controllerHeight: CGFloat { return 600 } lazy var sureButton:UIButton = { let button = UIButton(frame: CGRect(x: kScreenWidth-60, y: 0, width: 40, height: 40))
button.backgroundColor = .white button.addTarget(self, action: #selector(sureButtonClicked), for: .touchUpInside) button.layer.cornerRadius = 20 button.clipsToBounds = true return button }() lazy var containerView: UIView = { let view = UIView(frame: CGRect(x: 0, y: 75, width: kScreenWidth, height: kScreenHeight-75)) view.backgroundColor = UIColor.white return view }() lazy var titleLabel: UILabel = { let label = UILabel(frame:CGRect(x: (kScreenWidth-150)/2, y: 20, width: 150, height: 30)) label.textAlignment = .center label.text = "Select" label.font = UIFont.systemFont(ofSize: 20) return label }() override public func viewDidLoad() { super.viewDidLoad() config() } private func config() { view.backgroundColor = UIColor.clear let roundView = RoundView(frame: CGRect(x: 0, y: 0, width: kScreenWidth, height: 150)) view.addSubview(roundView) roundView.addSubview(titleLabel) view.addSubview(containerView) view.addSubview(sureButton) let segment1 = UISegmentedControl(items: ["Girl","Boy","Unsure"]) segment1.frame = CGRect(x: 20, y: 20, width: kScreenWidth-40, height: 35) segment1.selectedSegmentIndex = 0 segment1.tintColor = UIColor(red: 190/255, green: 31/255, blue: 109/255, alpha: 1) containerView.addSubview(segment1) let segment2 = UISegmentedControl(items: ["🍎","🍋","🍊"]) segment2.frame = CGRect(x: 20, y: 75, width: kScreenWidth-40, height: 35) segment2.selectedSegmentIndex = 1 segment2.tintColor = UIColor(red: 190/255, green: 31/255, blue: 109/255, alpha: 1) containerView.addSubview(segment2) let segment3 = UISegmentedControl(items: ["Home","Company","Parking Lot"]) segment3.frame = CGRect(x: 20, y: 130, width: kScreenWidth-40, height: 35) segment3.selectedSegmentIndex = 2 segment3.tintColor = UIColor(red: 190/255, green: 31/255, blue: 109/255, alpha: 1) containerView.addSubview(segment3) } @objc func sureButtonClicked() { self.dismiss(animated: true, completion: nil) } }
public class RoundView: UIView { public override init(frame: CGRect) { super.init(frame: frame) backgroundColor = UIColor.clear } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } public override func draw(_ rect: CGRect) { let color = UIColor.white color.set() let path = UIBezierPath(ovalIn: rect) path.fill() } }
|
最后调用
调用和系统调用类似,在第一个 Controller 上调用 self.presentBottom(HUTestVC())
。
参考资料
用UIPresentationController来写一个简洁漂亮的底部弹出控件