一. 背景
由于产品需求,希望一些弹框,只显示在首页,盖住整个屏幕,当然包括tabbar,点击跳转其他页面之后,弹框被盖住,返回来依然弹框依然显示。
二. 分析
我们分析这个需求的难点在于,如果弹框正常显示在当前UIApplication.shared.keyWindow或者UITabbarController的View上的话,弹框的层级是高于当前VC所在UINavigationController,因此点击跳转到其他页面之后,当前弹框依然会盖住跳转之后的VC。
三. 实现
以上的问题,有如下几种解决方法,我们具体对比一下。
A. 方案一
将弹框加在UIApplication.shared.keyWindow上,当push到其他页面时候,将弹框隐藏,返回当前页面之后再显示。
这里会存在一个问题就是苹果支持侧滑返回,所以显示和隐藏逻辑只能放在viewDidAppear和viewDidDisappear。这样就会造成显示的时候,突然闪现出来的问题。
B. 方案二
使用present半屏弹框样式,半屏弹框样式。这个方法显然也不行。
-
present半屏弹框样式,跟弹框盖住全屏不一致,且后面的VC也会缩小。 -
present半屏弹框样式,点击跳转其他VC也是半屏的。
C. 方案三
- 将
tabbarViewController作为UINavigationController的rootViewController,然后作为window的rootViewController
if let window = UIApplication.shared.delegate?.window {
let naviVC = UINavigationController.init(rootViewController: self.getTabBarController())
window?.rootViewController = naviVC
window?.makeKeyAndVisible()
}
然后弹框显示在UITabbarController的View,点击弹框跳转的时候获取window?.rootViewController及UINavigationController去进行相关push操作,这样跳转的页面的就能盖住弹框。因为跳转后的VC和UITabbarController是都是在同一个UINavigationController栈里面,后面的VC自然可以盖住前面的VC。
虽然这样可以有效的解决当前问题,但这里就改变了项目结构,导致很多地方的判断需要修改,比如判断当前页面是否为首页、还有路由框架,因为以前都是从UITabbarController去获取到对应的selectedViewController,即对应的UINavigationController去跳转,也就是UITabbarController有多个UINavigationController去管理对应的视图。
但现在由于是获取到了window?.rootViewController的UINavigationController去push,也就是UITabbarController的UINavigationController,就没使用到。
改动和测试范围就相对比较大。
D. 方案四
-
创建一个
Alert级别的UIWindow即dialogWindow, 显示级别高于UIApplication.shared.delegate.window -
然后设置该
UIWindow的rootViewController为UINavigationController -
UINavigationController的rootViewController为自定义的FJFDialogViewController -
FJFDialogViewController里面重写了loadView方法,生成自定义的FJFDialogView赋值给self.view。 -
FJFDialogViewController里面也重写了viewDidAppear和viewDidDisappear,在这两个方法里面分别取判断,当前AlertWindow是否应该显示。如果当前FJFDialogView的subviews数量为0,并且UINavigationController的viewControllers也为1,则将AlertWindow隐藏 -
如果当前
FJFDialogView的subviews数量大于0,或者UINavigationController的子viewControllers也为大于1,则将AlertWindow显示。 -
最后在FJFDialogView里面重新addSubview和willRemoveSubview方法,当调用addSubview添加子视图时,去判断当前dialogWindow是否应该显示,当调用willRemoveSubview,即表示子视图从dialogWindow移除时,去判断当前dialogWindow是否应该隐藏。
具体代码如下:
import UIKit
public class FJFDialogView: UIView {
// MARK: - Override
public override func addSubview(_ view: UIView) {
super.addSubview(view)
FJFDialogWindowManager.checkIsShowAlertWindow()
}
public override func willRemoveSubview(_ subview: UIView) {
super.willRemoveSubview(subview)
/// 因为willRemove还没有真正移除,所以加点延迟等真正移除后去判断
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.01) {
FJFDialogWindowManager.checkIsShowAlertWindow()
}
}
}
public class FJFDialogViewController: UIViewController {
// MARK: - Life
public override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
FJFDialogWindowManager.checkIsShowAlertWindow()
}
public override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
FJFDialogWindowManager.checkIsShowAlertWindow()
}
// MARK: - Override
public override func loadView() {
self.view = FJFDialogView(frame: UIScreen.main.bounds)
}
}
public class FJFDialogWindowManager {
// alter弹框显示 window
public static var dialogWindow: UIWindow?
// 获取 弹框 显示window
public class func getAlertShowWindow() -> UIWindow {
if let tmpWindow = self.dialogWindow {
return tmpWindow
}
var alertWindow = UIWindow(frame: UIScreen.main.bounds)
alertWindow.windowLevel = .alert
var navigationVc = UINavigationController.init(rootViewController: FJFDialogViewController.init())
navigationVc.view.backgroundColor = .clear
navigationVc.navigationBar.isHidden = true
alertWindow.rootViewController = navigationVc
alertWindow.makeKeyAndVisible()
alertWindow.isHidden = true
self.dialogWindow = alertWindow
return alertWindow
}
/// 隐藏 alert的window
public class func hiddenAlertWindow() {
self.dialogWindow?.isHidden = true
}
/// 校验是否 显示alert的window
public class func checkIsShowAlertWindow() {
let subViewCount = self.getAlertWindowView()?.subviews.count ?? 0
let subVcCount = self.getAlertWindowNavigationCount()
if subViewCount == 0, subVcCount == 1 {
self.dialogWindow?.isHidden = true
}
if subViewCount > 0 ||
subVcCount > 1 {
self.dialogWindow?.isHidden = false
}
}
/// 获取 弹框 window 显示view
public class func getAlertWindowView() -> UIView? {
if let rootVc = self.getAlertShowWindow().rootViewController,
let nav = rootVc as? UINavigationController,
let vc = nav.viewControllers.first {
return vc.view
}
return nil
}
/// 获取 弹框 window 显示view
public class func getAlertWindowNavigationCount() -> Int {
if let rootVc = self.getAlertShowWindow().rootViewController,
let nav = rootVc as? UINavigationController {
return nav.viewControllers.count
}
return 0
}
public class func destoryAlertShowWindow() {
self.dialogWindow?.rootViewController = nil
self.dialogWindow?.removeFromSuperview()
self.dialogWindow = nil
}
}
然后将路由里面改造下,添加参数是否来自于isAlertWindow,默认值为false。如果当前跳转来自alertWindow的弹框,就从alertWindow去获取跳转的UINavigationController.
// MARK: - 获取活跃VC
public class func getActivityViewController(_ isAlertWindow: Bool = false) -> UIViewController? {
var viewController: UIViewController?
let windows: [UIWindow] = UIApplication.shared.windows
var activeWindow: UIWindow?
for window in windows {
if isAlertWindow {
if window.windowLevel == UIWindow.Level.alert {
activeWindow = window
break
}
} else {
if window.windowLevel == UIWindow.Level.normal {
activeWindow = window
break
}
}
}
if activeWindow != nil && activeWindow!.subviews.count > 0 {
let frontView: UIView = activeWindow!.subviews.last!
var nextResponder: UIResponder? = frontView.next
while nextResponder!.isKind(of: UIWindow.self) == false {
if nextResponder!.isKind(of: UIViewController.self) {
viewController = nextResponder as! UIViewController?
break
} else {
nextResponder = nextResponder!.next
}
}
if nextResponder!.isKind(of: UIViewController.self) {
viewController = nextResponder as! UIViewController?
} else {
viewController = activeWindow!.rootViewController
}
while viewController!.presentedViewController != nil {
viewController = viewController!.presentedViewController
}
}
return viewController
}
- 这样改动只针对这种类型的弹框,范围相对可控,测试进行测试也只需要关注这几个弹框。