因为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 那可以设定弹出的动画效果,以及外界该如何呼叫我们的弹出视窗。
连续 30 天不中断每天上传一支教学影片,教你如何用 React 加上 Firebase 打造社群...
中秋假期最後一天,吃了美式、韩式及日式烧肉,吃了这麽多天大鱼大肉可能有点腻,最後一天想来点清爽的肉肉...
在昨天的文章中,快速而简洁地向各位介绍语音对话设计中「测试与迭代」的相关名词 在今天的文章中我们将...
前言 昨天相邻矩阵权重图,没有连结到的点 从 0 修改为 ∞ ,可以想像没有连结到的节点为不可能连结...
在开始前我们先来介绍一些看似基础但却很重要的工具吧。 var、let和const var、let、c...