前言
因为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 那可以设定弹出的动画效果,以及外界该如何呼叫我们的弹出视窗。