前言
开发时经常会自定义一些弹出视图,比如弹框(Alert)、底部弹出框(Action Sheet)等。自定义弹出视图有许多方法,到底哪种才更正确呢?下面我列举几种方法,一起讨论。先附上我的方案https://github.com/CatchZeng/SwiftPopup
addSubview
顾名思义,就是造出一个 View,然后 add 到 ViewController 的 View 上显示出来,如https://www.jianshu.com/p/de2ecfd770c2的做法。
- 好处:够简单
- 坏处:视图&逻辑都在 View 上,职能不清晰,扩展性差,显示依赖于 ViewController
addChildViewController
使用 addChildViewController 方法,将视图显示出来。如 https://www.jianshu.com/p/6d790e6eb2ba 的做法。
- 好处:简单、职能清晰…
- 坏处:在导航栏控制器或者标签栏控制器下弹出,会出现没完全覆盖到的情况。
window
新建一个 window,切换成 KeyWindow,将弹出视图作为 rootViewController。代码如下
1
2
3
4
5
6
7
8
func show() {
previousWindow = UIApplication.shared.keyWindow
keyWindow = UIWindow(frame: UIScreen.main.bounds)
keyWindow?.rootViewController = self
keyWindow?.windowLevel = UIWindowLevelNormal + 100
keyWindow?.backgroundColor = UIColor.clear
keyWindow?.makeKeyAndVisible()
}
- 好处:简单、职能清晰…
- 坏处:在横屏的时候,第二次弹出时,出现显示问题【暂时不知道为什么】
UIPresentationController
使用 UIPresentationController 自定义转场动画,这也是我的实现方法。 UIPresentationController 是提供高级视图切换的类。它让管理 present ViewController 的过程变得简单。
Presentation
如下图弹出一个 UIViewController,可以和用户交互的 Controller 叫做 PresentedViewController,而后面那个被部分遮挡的 UIViewController 叫做 PresentingViewController.
UIPopoverController 的使用方法
- 设置 style&delegate
1
2
modalPresentationStyle = .custom
transitioningDelegate = self
- 实现 delegate,此代理需要 UIPresentationController,以及 UIViewControllerAnimatedTransitioning 来自定义转场的动画
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
extension SwiftPopup: UIViewControllerTransitioningDelegate {
public func presentationController(forPresented presented: UIViewController,
presenting: UIViewController?,
source: UIViewController) -> UIPresentationController? {
let controller = SwiftPopupPresentationController(presentedViewController: presented,
presenting: presenting)
controller.backViewColor = backViewColor
return controller
}
public func animationController(forPresented presented: UIViewController,
presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return showAnimation
}
public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return dismissAnimation
}
}
- 实现自定义的 UIPresentationController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
open class SwiftPopupPresentationController: UIPresentationController {
// MARK: Override Methods
// 将要开始转场
override open func presentationTransitionWillBegin() {
}
// 将要移除页面
override open func dismissalTransitionWillBegin() {
}
// 是否要移除PresentersView,也就是转场之前的视图,这里为false,因为弹框需要看到之前的视图
override open var shouldRemovePresentersView: Bool {
return false
}
- 实现自定义的动画
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
open class SwiftPopupShowAnimation: NSObject, UIViewControllerAnimatedTransitioning {
public var duration: TimeInterval = 0.8
public var delay: TimeInterval = 0.0
public var springWithDamping: CGFloat = 0.8
public var springVelocity: CGFloat = 2.0
open func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
open func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let toViewController = transitionContext.viewController( forKey: UITransitionContextViewControllerKey.to),
let toView = transitionContext.view( forKey: UITransitionContextViewKey.to)
else {
return
}
let containerView = transitionContext.containerView
toView.frame = transitionContext.finalFrame(for: toViewController)
containerView.addSubview(toView)
toView.transform = CGAffineTransform(scaleX: 0.01, y: 0.01)
UIView.animate(withDuration: duration,
delay: delay,
usingSpringWithDamping: springWithDamping,
initialSpringVelocity: springVelocity,
options: .curveEaseInOut, animations: {
toView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
}) { (finished) in
transitionContext.completeTransition(finished)
}
}
}
- Show
1
2
3
4
5
6
public func show(above viewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController,
completion: (()-> Void)? = nil) {
mIsShowing = true
viewController?.present(self, animated: true, completion: completion)
}
- Dismiss
1
2
3
4
5
6
public func dismiss(completion: (()-> Void)? = nil) {
dismiss(animated: true) {
self.mIsShowing = false
completion?()
}
}