一. 布景
由于产品需求,期望一些弹框,只显现在主页,盖住整个屏幕,当然包括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
}
- 这样改动只针对这种类型的弹框,规模相对可控,测验进行测验也只需求重视这几个弹框。