一. 布景

由于产品需求,期望一些弹框,只显现在主页,盖住整个屏幕,当然包括tabbar,点击跳转其他页面之后,弹框被盖住,回来来仍然弹框仍然显现。

iOS 弹框盖住Tabbar,跳转其他页面回来仍然显现计划比照

二. 分析

咱们分析这个需求的难点在于,假如弹框正常显现在当时UIApplication.shared.keyWindow或者UITabbarControllerView上的话,弹框的层级是高于当时VC地点UINavigationController,因而点击跳转到其他页面之后,当时弹框仍然会盖住跳转之后的VC

三. 实现

以上的问题,有如下几种处理办法,咱们具体比照一下。

A. 计划一

将弹框加在UIApplication.shared.keyWindow上,当push到其他页面时候,将弹框隐藏,回来当时页面之后再显现。

这里会存在一个问题便是苹果支撑侧滑回来,所以显现和隐藏逻辑只能放在viewDidAppearviewDidDisappear。这样就会形成显现的时候,忽然闪现出来的问题。

iOS 弹框盖住Tabbar,跳转其他页面回来仍然显现计划比照

B. 计划二

使用present半屏弹框款式,半屏弹框款式。这个办法明显也不可。

  • present半屏弹框款式,跟弹框盖住全屏不一致,且后边的VC也会缩小。

  • present半屏弹框款式,点击跳转其他VC也是半屏的。

iOS 弹框盖住Tabbar,跳转其他页面回来仍然显现计划比照

C. 计划三

  • tabbarViewController作为UINavigationControllerrootViewController,然后作为windowrootViewController
if let window = UIApplication.shared.delegate?.window {
      let naviVC = UINavigationController.init(rootViewController: self.getTabBarController())
      window?.rootViewController = naviVC
      window?.makeKeyAndVisible()
 }

然后弹框显现在UITabbarControllerView,点击弹框跳转的时候获取window?.rootViewControllerUINavigationController去进行相关push操作,这样跳转的页面的就能盖住弹框。由于跳转后的VCUITabbarController是都是在同一个UINavigationController栈里边,后边的VC自然能够盖住前面的VC

尽管这样能够有效的处理当时问题,但这里就改变了项目结构,导致许多当地的判别需求修改,比如判别当时页面是否为主页、还有路由框架,由于曾经都是从UITabbarController去获取到对应的selectedViewController,即对应的UINavigationController去跳转,也便是UITabbarController有多个UINavigationController去办理对应的视图。

但现在由于是获取到了window?.rootViewControllerUINavigationControllerpush,也便是UITabbarControllerUINavigationController,就没使用到。

改动和测验规模就相比照较大。

D. 计划四

  • 创建一个Alert等级的UIWindowdialogWindow, 显现等级高于UIApplication.shared.delegate.window

  • 然后设置该UIWindowrootViewControllerUINavigationController

  • UINavigationControllerrootViewController为自定义的FJFDialogViewController

  • FJFDialogViewController里边重写了loadView办法,生成自定义的FJFDialogView赋值self.view

  • FJFDialogViewController里边也重写了viewDidAppearviewDidDisappear,在这两个办法里边别离取判别,当时AlertWindow是否应该显现。假如当时FJFDialogViewsubviews数量为0,而且UINavigationControllerviewControllers也为1,则将AlertWindow隐藏

  • 假如当时FJFDialogViewsubviews数量大于0,或者UINavigationController的子viewControllers也为大于1,则将AlertWindow显现。

  • 最终在FJFDialogView里边从头addSubviewwillRemoveSubview办法,当调用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
    }
  • 这样改动只针对这种类型的弹框,规模相对可控,测验进行测验也只需求重视这几个弹框。