当前位置: 首页 > news >正文

Swift 项目总结 06 基于控制器的全局状态栏管理

发现问题

全局管理和局部管理状态栏

iOS 7 以前,我们只有基于 UIApplication 单例类的全局状态栏管理:

extension UIApplication {
    // Setting the statusBarStyle does nothing if your application is using the default UIViewController-based status bar system.
    @available(iOS, introduced: 2.0, deprecated: 9.0, message: "Use -[UIViewController preferredStatusBarStyle]")
    open func setStatusBarStyle(_ statusBarStyle: UIStatusBarStyle, animated: Bool)
    
    // Setting statusBarHidden does nothing if your application is using the default UIViewController-based status bar system.
    @available(iOS, introduced: 3.2, deprecated: 9.0, message: "Use -[UIViewController prefersStatusBarHidden]")
    open func setStatusBarHidden(_ hidden: Bool, with animation: UIStatusBarAnimation)
}
复制代码

我们使用起来大概这样:

// 设置状态栏样式
UIApplication.shared.statusBarStyle = .default
// 设置状态栏是否隐藏
UIApplication.shared.isStatusBarHidden = false
// 设置状态栏是否隐藏,变化过程是否需要动画
UIApplication.shared.setStatusBarHidden(false, with: .fade)
复制代码

但在 iOS 7 以后,苹果推出了另外一套状态栏管理机制,即基于控制器的局部状态栏管理,从官方注释可以看出这种机制是苹果推荐使用的:

extension UIViewController {
    @available(iOS 7.0, *)
    open var preferredStatusBarStyle: UIStatusBarStyle { get } // Defaults to UIStatusBarStyleDefault

    @available(iOS 7.0, *)
    open var prefersStatusBarHidden: Bool { get } // Defaults to NO

    // Override to return the type of animation that should be used for status bar changes for this view controller. This currently only affects changes to prefersStatusBarHidden.
    @available(iOS 7.0, *)
    open var preferredStatusBarUpdateAnimation: UIStatusBarAnimation { get } // Defaults to UIStatusBarAnimationFade

    // 手动触发状态栏状态更新
    @available(iOS 7.0, *)
    open func setNeedsStatusBarAppearanceUpdate()
}
复制代码

我们使用起来大概这样:

class ViewController: UIViewController {
    // 状态栏是否隐藏
    override var prefersStatusBarHidden: Bool {
        return false
    }
    // 状态栏样式
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return .default
    }
    // 状态栏隐藏动画
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        return .fade
    }
}
复制代码

默认情况下,状态栏都是基于控制器的状态管理,这 2 种状态栏管理机制可以通过在 info.plist 修改配置进行选择

基于控制器的全局管理单例类

2 种管理状态栏的形式各有优缺点:

全局管理

  • 优点:管理方便,代码简洁
  • 缺点:状态是全局共享的,相互影响

局部管理

  • 优点:状态是分离到各个控制器,互不影响
  • 缺点:管理不方便,管理代码分散到各个控制器

我想结合了这 2 种管理机制的优点,开发一个基于控制器的全局管理单例类 StatusBarManager,即能像 UIApplication 那样简洁的管理状态栏,又能像 UIViewController 那样分离的管理状态栏。

分析问题

基于控制器的全局管理实现

首先我们就需要先把基于控制器的管理状态栏转变成单例类管理状态栏,这很简单,类似下面这样实现,具体内部实现下面会给出源码:

/// 自定义基类控制器,重载 prefersStatusBarHidden 等方法
class BasicViewController: UIViewController {
    
    override var prefersStatusBarHidden: Bool {
        return StatusBarManager.shared.isHidden
    }
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return StatusBarManager.shared.style
    }
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        return StatusBarManager.shared.animation
    }
}
复制代码

分离的状态栏状态实现

实现分离的状态管理才是我们的重点,不然我们直接使用全局管理就行了,没必要这么麻烦,首先我们要理解什么是分离的状态管理,先看下图:

当我们显示前面视图时,后面视图的状态栏变化是不会影响到前面视图的状态栏状态,这就是状态栏状态的分离。

要实现这个功能,我联系到控制器导航使用的是 push 和 pop 操作,present 和 dismiss 操作也可以看成一个 push 和 pop 操作,我就想那我的状态栏状态是否也能通过 push 和 pop 进行管理呢?

看起来是可以,但我们要想到另外一个问题,那就是分页控制器用 push 和 pop 管理状态栏状态是不行的,因为是同层级控制器,那就需要有多分支的存储结构,不能用线性表结构,我想到了数据结构里的树结构!再仔细一想,UIView 视图层次不就是一个树结构吗?好,就决定是你了!!!

解决问题

树结构示意图:

import UIKit

/// 状态栏单一状态节点
class StatusBarState: NSObject {
    static let defaultKey: String = "StatusBarState.default.root.key"
    
    var isHidden: Bool = false
    var style: UIStatusBarStyle = .lightContent
    var animation: UIStatusBarAnimation = .fade
    var key: String = defaultKey
    // 子节点数组
    var subStates: [StatusBarState] = []
    // 父节点,为 nil 说明是根节点
    weak var superState: StatusBarState?
    // 下一个路径节点,为 nil 说明是叶子节点
    weak var nextState: StatusBarState?
    
    override var description: String {
        return "{ key=\(self.key) selected=\(String(describing: self.nextState?.key)) }"
    }
}

/// 全局状态栏状态管理单例类
class StatusBarManager {
    static let shared = StatusBarManager()
    // MARK: - 属性
    /// 状态键集合,用来判断树中是否有某个状态
    fileprivate var stateKeys: Set<String> = Set<String>()
    /// 根节点状态,从这个根节点可以遍历到整个状态树
    fileprivate var rootState: StatusBarState!
    /// 更新状态栏动画时间
    fileprivate var duration: TimeInterval = 0.1
    /// 当前状态
    fileprivate var currentState: StatusBarState!
    
    /// 以下3个计算属性都是取当前状态显示以及更新当前状态
    var isHidden: Bool {
        get {
            return currentState.isHidden
        }
        set {
            setState(for: currentState.key, isHidden: newValue)
        }
    }
    var style: UIStatusBarStyle {
        get {
            return currentState.style
        }
        set {
            setState(for: currentState.key, style: newValue)
        }
    }
    var animation: UIStatusBarAnimation {
        get {
            return currentState.animation
        }
        set {
            setState(for: currentState.key, animation: newValue)
        }
    }
    
    // MARK: - 方法
    /// 初始化根节点
    fileprivate init() {
        rootState = StatusBarState()
        currentState = rootState
        stateKeys.insert(rootState.key)
    }
    
    /// 为某个状态(root)添加子状态(key),当 root = nil 时,表示添加到根状态上
    @discardableResult
    func addSubState(with key: String, root: String? = nil) -> StatusBarState? {
        guard !stateKeys.contains(key) else { return nil }
        stateKeys.insert(key)
        
        let newState = StatusBarState()
        newState.key = key
        
        // 找到键为 root 的父状态
        var superState: StatusBarState! = rootState
        if let root = root {
            superState = findState(root)
        }
        newState.isHidden = superState.isHidden
        newState.style = superState.style
        newState.animation = superState.animation
        newState.superState = superState
        
        // 添加进父状态的子状态集合中,默认选中第一个
        superState.subStates.append(newState)
        if superState.nextState == nil {
            superState.nextState = newState
        }
        
        // 判断是否在当前状态上添加子状态,是的话,自动切换当前状态
        if currentState.key == superState.key {
            currentState = newState
            updateStatusBar()
        }
        
        printAllStates()
        return newState
    }
    
    /// 删除某个状态及其子状态树
    func removeState(with key: String) {
        guard stateKeys.contains(key) else { return }
        let state = findState(key)
        let isContainCurrentState = findStateInTree(state, key: currentState.key) != nil
        if state.subStates.count > 0 {
            removeSubStatesInTree(state)
        }
        // 是否有父状态,如果没有,说明要删除的是根状态,根节点是不能删除的,否则删除该节点并切换当前状态
        if let superState = state.superState {
            stateKeys.remove(state.key)
            if let index = superState.subStates.index(of: state) {
                superState.subStates.remove(at: index)
            }
            superState.nextState = superState.subStates.first
            if isContainCurrentState {
                if let selectedState = superState.nextState {
                    currentState = selectedState
                } else {
                    currentState = superState
                }
                updateStatusBar()
            }
            
        }
        printAllStates()
    }
    
    /// 更改某个状态(root)下要显示直接的子状态节点(key)
    func showState(for key: String, root: String? = nil) {
        guard stateKeys.contains(key) else { return }
        
        // 改变父状态 nextState 属性
        let rootState = findState(root)
        for subState in rootState.subStates {
            if subState.key == key {
                rootState.nextState = subState
                break
            }
        }
        // 找到切换后的当前状态
        let newCurrentState = findCurrentStateInTree(rootState)
        if newCurrentState != currentState {
            currentState = newCurrentState
            updateStatusBar()
        }
        printAllStates()
    }
    
    /// 删除某个状态下的子状态树
    func clearSubStates(with key: String, isUpdate: Bool = true) {
        guard stateKeys.contains(key) else { return }
        let state = findState(key)
        var needUpdate: Bool = false
        if findStateInTree(state, key: currentState.key) != nil {
            currentState = state
            needUpdate = true
        }
        if state.subStates.count > 0 {
            removeSubStatesInTree(state)
        }
        if needUpdate && isUpdate {
            updateStatusBar()
        }
        printAllStates()
    }
    
    /// 负责打印状态树结构
    func printAllStates(_ method: String = #function) {
        debugPrint("\(method): currentState = \(currentState.key)")
        printAllStatesInTree(rootState, deep: 0, method: method)
    }

    /// 更新栈中 key 对应的状态,key == nil 表示栈顶状态
    func setState(for key: String? = nil, isHidden: Bool? = nil, style: UIStatusBarStyle? = nil, animation: UIStatusBarAnimation? = nil) {
        var needUpdate: Bool = false
        let state = findState(key)
        if let isHidden = isHidden, state.isHidden != isHidden {
            needUpdate = true
            state.isHidden = isHidden
        }
        if let style = style, state.style != style {
            needUpdate = true
            state.style = style
        }
        if let animation = animation, state.animation != animation {
            needUpdate = true
            state.animation = animation
        }
        // key != nil 表示更新对应 key 的状态,需要判断该状态是否是当前状态
        if let key = key {
            guard let currentState = currentState, currentState.key == key else { return }
        }
        // 状态有变化才需要更新视图
        if needUpdate {
            updateStatusBar()
        }
    }
    
    /// 开始更新状态栏的状态
    fileprivate func updateStatusBar() {
        DispatchQueue.main.async { // 在主线程异步执行 避免同时索取同一属性
            // 如果状态栏需要动画(fade or slide),需要添加动画时间,才会有动画效果
            UIView.animate(withDuration: self.duration, animations: {
                UIApplication.shared.keyWindow?.rootViewController?.setNeedsStatusBarAppearanceUpdate()
            })
        }
    }
    
    /// 从状态树中找到对应的节点状态,没找到就返回根节点
    fileprivate func findState(_ key: String? = nil) -> StatusBarState {
        if let key = key { // 查找
            if let findState = findStateInTree(rootState, key: key) {
                return findState
            }
        }
        return rootState
    }
    
    /// 从状态树中找到对应的节点状态的递归方法
    fileprivate func findStateInTree(_ state: StatusBarState, key: String) -> StatusBarState? {
        if state.key == key {
            return state
        }
        for subState in state.subStates {
            if let findState = findStateInTree(subState, key: key) {
                return findState
            }
        }
        return nil
    }
    
    /// 删除某个状态下的所有子状态的递归方法
    fileprivate func removeSubStatesInTree(_ state: StatusBarState) {
        state.subStates.forEach { (subState) in
            stateKeys.remove(subState.key)
            removeSubStatesInTree(subState)
        }
        state.subStates.removeAll()
    }
    
    /// 找到某个状态下的最底层状态
    fileprivate func findCurrentStateInTree(_ state: StatusBarState) -> StatusBarState? {
        if let nextState = state.nextState {
            return findCurrentStateInTree(nextState)
        }
        return state
    }
    
    /// 打印状态树结构的递归方法
    fileprivate func printAllStatesInTree(_ state: StatusBarState, deep: Int = 0, method: String) {
        debugPrint("\(method): \(deep) - state=\(state)")
        for subState in state.subStates {
            printAllStatesInTree(subState, deep: deep + 1, method: method)
        }
    }
}
复制代码

创建 UIViewController+StatusBar 分类和基类控制器来辅助设置,简单管理状态栏:

/// UIViewController+StatusBar.swift
import UIKit

extension UIViewController {
    
    /// 控制器的状态栏唯一键
    var statusBarKey: String {
        return "\(self)"
    }
    
    /// 设置该控制器的状态栏状态
    func setStatusBar(isHidden: Bool? = nil, style: UIStatusBarStyle? = nil, animation: UIStatusBarAnimation? = nil) {
        StatusBarManager.shared.setState(for: statusBarKey, isHidden: isHidden, style: style, animation: animation)
    }

    /// 添加一个子状态
    func addSubStatusBar(for viewController: UIViewController) {
        let superKey = self.statusBarKey
        let subKey = viewController.statusBarKey
        StatusBarManager.shared.addSubState(with: subKey, root: superKey)
    }
    
    /// 批量添加子状态,树横向生长
    func addSubStatusBars(for viewControllers: [UIViewController]) {
        viewControllers.forEach { (viewController) in
            self.addSubStatusBar(for: viewController)
        }
    }
    
    /// 从整个状态树上删除当前状态
    func removeFromSuperStatusBar() {
        let key = self.statusBarKey
        StatusBarManager.shared.removeState(with: key)
    }
    
    /// 设置当前状态下的所有子状态
    func setSubStatusBars(for viewControllers: [UIViewController]?) {
        clearSubStatusBars()
        if let viewControllers = viewControllers {
            addSubStatusBars(for: viewControllers)
        }
    }
    
    /// 通过类似压栈的形式,压入一组状态,树纵向生长
    func pushStatusBars(for viewControllers: [UIViewController]) {
        var lastViewController: UIViewController? = self
        viewControllers.forEach { (viewController) in
            if let superController = lastViewController {
                superController.addSubStatusBar(for: viewController)
                lastViewController = viewController
            }
        }
    }
    
    /// 切换多个子状态的某个子状态
    func showStatusBar(for viewController: UIViewController?) {
        guard let viewController = viewController else { return }
        let superKey = self.statusBarKey
        let subKey = viewController.statusBarKey
        StatusBarManager.shared.showState(for: subKey, root: superKey)
    }
    
    /// 清除所有子状态
    func clearSubStatusBars(isUpdate: Bool = true) {
        StatusBarManager.shared.clearSubStates(with: self.statusBarKey, isUpdate: isUpdate)
    }
}
复制代码
/// 保证所有控制器都重载了 prefersStatusBarHidden 的方法
class BasicViewController: UIViewController {
    
    override var prefersStatusBarHidden: Bool {
        return StatusBarManager.shared.isHidden
    }
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return StatusBarManager.shared.style
    }
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        return StatusBarManager.shared.animation
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    deinit {
         self.removeFromSuperStatusBar()
    }
    
    override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
        self.addSubStatusBar(for: viewControllerToPresent)
        super.present(viewControllerToPresent, animated: flag, completion: completion)
    }
}

/// 保证所有控制器都重载了 prefersStatusBarHidden 的方法
class BasicNavigationController: UINavigationController {
    
    override var prefersStatusBarHidden: Bool {
        return StatusBarManager.shared.isHidden
    }
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return StatusBarManager.shared.style
    }
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        return StatusBarManager.shared.animation
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        pushStatusBars(for: viewControllers)
    }
    
    override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) {
        clearSubStatusBars(isUpdate: false)
        pushStatusBars(for: viewControllers)
        super.setViewControllers(viewControllers, animated: animated)
    }
    
    override func pushViewController(_ viewController: UIViewController, animated: Bool) {
        topViewController?.addSubStatusBar(for: viewController)
        super.pushViewController(viewController, animated: animated)
    }
}

/// 保证所有控制器都重载了 prefersStatusBarHidden 的方法
class BasicTabBarController: UITabBarController, UITabBarControllerDelegate {
    
    override var prefersStatusBarHidden: Bool {
        return StatusBarManager.shared.isHidden
    }
    override var preferredStatusBarStyle: UIStatusBarStyle {
        return StatusBarManager.shared.style
    }
    override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
        return StatusBarManager.shared.animation
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.setSubStatusBars(for: viewControllers)
        self.delegate = self
    }
    
    override func setViewControllers(_ viewControllers: [UIViewController]?, animated: Bool) {
        self.setSubStatusBars(for: viewControllers)
        super.setViewControllers(viewControllers, animated: animated)
    }
    
    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
        showStatusBar(for: viewController)
    }
}
复制代码

基于控制器的全局状态栏使用:

class ViewController: BasicViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setStatusBar(isHidden: false, style: .default)
    }
}
复制代码

Demo 源码在这里:StatusBarManagerDemo

有什么问题可以在下方评论区提出,写得不好可以提出你的意见,我会合理采纳的,O(∩_∩)O哈哈~,求关注求赞

转载于:https://juejin.im/post/5b126ea76fb9a01e75462962

相关文章:

  • 织梦网站安装目录更换后,怎么更换上传网站中图片路径
  • 【Android Studio安装部署系列】二十四、Android studio中Gradle插件版本和Gradle版本关系...
  • 基于 HTML5 WebGL 的 3D 棉花加工监控系统
  • 16-client、offset、scroll系列
  • 数据结构之 二叉树
  • 【Touchinput 】指定输入方法类型(11)
  • iOS中父类readonly属性修改
  • μCOS-II系统之事件(event)的使用规则及MUTEX实例
  • 之所以一无所成,并不是我们不够努力
  • [转]Ubuntu16 压缩解压文件命令
  • 数组全部整理
  • corosync+pacemaker配置高可用集群(需要额外安装crm工具)
  • Vmvare 虚拟机固定IP
  • 用Linux shell 计算两个时间差
  • Git 打补丁流程
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • go语言学习初探(一)
  • jdbc就是这么简单
  • JS数组方法汇总
  • Linux CTF 逆向入门
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 阿里云应用高可用服务公测发布
  • 道格拉斯-普克 抽稀算法 附javascript实现
  • 湖南卫视:中国白领因网络偷菜成当代最寂寞的人?
  • 欢迎参加第二届中国游戏开发者大会
  • 计算机常识 - 收藏集 - 掘金
  • 物联网链路协议
  • 详解NodeJs流之一
  • 智能合约开发环境搭建及Hello World合约
  • 做一名精致的JavaScripter 01:JavaScript简介
  • Nginx实现动静分离
  • 第二十章:异步和文件I/O.(二十三)
  • ​​​​​​​ubuntu16.04 fastreid训练过程
  • ​草莓熊python turtle绘图代码(玫瑰花版)附源代码
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • #{}和${}的区别?
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #NOIP 2014# day.1 T2 联合权值
  • (echarts)echarts使用时重新加载数据之前的数据存留在图上的问题
  • (超详细)语音信号处理之特征提取
  • (定时器/计数器)中断系统(详解与使用)
  • (多级缓存)多级缓存
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (十三)Flask之特殊装饰器详解
  • (原創) 博客園正式支援VHDL語法著色功能 (SOC) (VHDL)
  • (转)ObjectiveC 深浅拷贝学习
  • (转)shell中括号的特殊用法 linux if多条件判断
  • (转载)Google Chrome调试JS
  • .MyFile@waifu.club.wis.mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET Framework .NET Core与 .NET 的区别
  • .net程序集学习心得
  • .NET开源快速、强大、免费的电子表格组件
  • .NET命名规范和开发约定
  • @PreAuthorize注解