發(fā)現(xiàn)問題
全局管理和局部管理狀態(tài)欄
iOS 7 以前,我們只有基于 UIApplication 單例類的全局狀態(tài)欄管理:
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)
}
我們使用起來大概這樣:
// 設(shè)置狀態(tài)欄樣式
UIApplication.shared.statusBarStyle = .default
// 設(shè)置狀態(tài)欄是否隱藏
UIApplication.shared.isStatusBarHidden = false
// 設(shè)置狀態(tài)欄是否隱藏店茶,變化過程是否需要?jiǎng)赢?UIApplication.shared.setStatusBarHidden(false, with: .fade)
但在 iOS 7 以后蛹找,蘋果推出了另外一套狀態(tài)欄管理機(jī)制姨伤,即基于控制器的局部狀態(tài)欄管理,從官方注釋可以看出這種機(jī)制是蘋果推薦使用的:
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
// 手動(dòng)觸發(fā)狀態(tài)欄狀態(tài)更新
@available(iOS 7.0, *)
open func setNeedsStatusBarAppearanceUpdate()
}
我們使用起來大概這樣:
class ViewController: UIViewController {
// 狀態(tài)欄是否隱藏
override var prefersStatusBarHidden: Bool {
return false
}
// 狀態(tài)欄樣式
override var preferredStatusBarStyle: UIStatusBarStyle {
return .default
}
// 狀態(tài)欄隱藏動(dòng)畫
override var preferredStatusBarUpdateAnimation: UIStatusBarAnimation {
return .fade
}
}
默認(rèn)情況下熄赡,狀態(tài)欄都是基于控制器的狀態(tài)管理姜挺,這 2 種狀態(tài)欄管理機(jī)制可以通過在 info.plist 修改配置進(jìn)行選擇
基于控制器的全局管理單例類
2 種管理狀態(tài)欄的形式各有優(yōu)缺點(diǎn):
全局管理
- 優(yōu)點(diǎn):管理方便,代碼簡(jiǎn)潔
- 缺點(diǎn):狀態(tài)是全局共享的彼硫,相互影響
局部管理
- 優(yōu)點(diǎn):狀態(tài)是分離到各個(gè)控制器炊豪,互不影響
- 缺點(diǎn):管理不方便凌箕,管理代碼分散到各個(gè)控制器
我想結(jié)合了這 2 種管理機(jī)制的優(yōu)點(diǎn),開發(fā)一個(gè)基于控制器的全局管理單例類 StatusBarManager
词渤,即能像 UIApplication
那樣簡(jiǎn)潔的管理狀態(tài)欄牵舱,又能像 UIViewController
那樣分離的管理狀態(tài)欄。
分析問題
基于控制器的全局管理實(shí)現(xiàn)
首先我們就需要先把基于控制器的管理狀態(tài)欄轉(zhuǎn)變成單例類管理狀態(tài)欄缺虐,這很簡(jiǎn)單芜壁,類似下面這樣實(shí)現(xiàn),具體內(nèi)部實(shí)現(xiàn)下面會(huì)給出源碼:
/// 自定義基類控制器高氮,重載 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
}
}
分離的狀態(tài)欄狀態(tài)實(shí)現(xiàn)
實(shí)現(xiàn)分離的狀態(tài)管理才是我們的重點(diǎn)慧妄,不然我們直接使用全局管理就行了,沒必要這么麻煩剪芍,首先我們要理解什么是分離的狀態(tài)管理塞淹,先看下圖:
當(dāng)我們顯示前面視圖時(shí),后面視圖的狀態(tài)欄變化是不會(huì)影響到前面視圖的狀態(tài)欄狀態(tài)罪裹,這就是狀態(tài)欄狀態(tài)的分離饱普。
要實(shí)現(xiàn)這個(gè)功能,我聯(lián)系到控制器導(dǎo)航使用的是 push 和 pop 操作状共,present 和 dismiss 操作也可以看成一個(gè) push 和 pop 操作套耕,我就想那我的狀態(tài)欄狀態(tài)是否也能通過 push 和 pop 進(jìn)行管理呢?
看起來是可以峡继,但我們要想到另外一個(gè)問題冯袍,那就是分頁控制器用 push 和 pop 管理狀態(tài)欄狀態(tài)是不行的,因?yàn)槭峭瑢蛹?jí)控制器鬓椭,那就需要有多分支的存儲(chǔ)結(jié)構(gòu)颠猴,不能用線性表結(jié)構(gòu),我想到了數(shù)據(jù)結(jié)構(gòu)里的樹結(jié)構(gòu)小染!再仔細(xì)一想翘瓮,UIView 視圖層次不就是一個(gè)樹結(jié)構(gòu)嗎?好裤翩,就決定是你了W手选!踊赠!
解決問題
樹結(jié)構(gòu)示意圖:
import UIKit
/// 狀態(tài)欄單一狀態(tài)節(jié)點(diǎn)
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
// 子節(jié)點(diǎn)數(shù)組
var subStates: [StatusBarState] = []
// 父節(jié)點(diǎn)呵扛,為 nil 說明是根節(jié)點(diǎn)
weak var superState: StatusBarState?
// 下一個(gè)路徑節(jié)點(diǎn),為 nil 說明是葉子節(jié)點(diǎn)
weak var nextState: StatusBarState?
override var description: String {
return "{ key=\(self.key) selected=\(String(describing: self.nextState?.key)) }"
}
}
/// 全局狀態(tài)欄狀態(tài)管理單例類
class StatusBarManager {
static let shared = StatusBarManager()
// MARK: - 屬性
/// 狀態(tài)鍵集合筐带,用來判斷樹中是否有某個(gè)狀態(tài)
fileprivate var stateKeys: Set<String> = Set<String>()
/// 根節(jié)點(diǎn)狀態(tài)今穿,從這個(gè)根節(jié)點(diǎn)可以遍歷到整個(gè)狀態(tài)樹
fileprivate var rootState: StatusBarState!
/// 更新狀態(tài)欄動(dòng)畫時(shí)間
fileprivate var duration: TimeInterval = 0.1
/// 當(dāng)前狀態(tài)
fileprivate var currentState: StatusBarState!
/// 以下3個(gè)計(jì)算屬性都是取當(dāng)前狀態(tài)顯示以及更新當(dāng)前狀態(tài)
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: - 方法
/// 初始化根節(jié)點(diǎn)
fileprivate init() {
rootState = StatusBarState()
currentState = rootState
stateKeys.insert(rootState.key)
}
/// 為某個(gè)狀態(tài)(root)添加子狀態(tài)(key),當(dāng) root = nil 時(shí)伦籍,表示添加到根狀態(tài)上
@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 的父狀態(tài)
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
// 添加進(jìn)父狀態(tài)的子狀態(tài)集合中蓝晒,默認(rèn)選中第一個(gè)
superState.subStates.append(newState)
if superState.nextState == nil {
superState.nextState = newState
}
// 判斷是否在當(dāng)前狀態(tài)上添加子狀態(tài)腮出,是的話,自動(dòng)切換當(dāng)前狀態(tài)
if currentState.key == superState.key {
currentState = newState
updateStatusBar()
}
printAllStates()
return newState
}
/// 刪除某個(gè)狀態(tài)及其子狀態(tài)樹
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)
}
// 是否有父狀態(tài)芝薇,如果沒有胚嘲,說明要?jiǎng)h除的是根狀態(tài),根節(jié)點(diǎn)是不能刪除的洛二,否則刪除該節(jié)點(diǎn)并切換當(dāng)前狀態(tài)
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()
}
/// 更改某個(gè)狀態(tài)(root)下要顯示直接的子狀態(tài)節(jié)點(diǎn)(key)
func showState(for key: String, root: String? = nil) {
guard stateKeys.contains(key) else { return }
// 改變父狀態(tài) nextState 屬性
let rootState = findState(root)
for subState in rootState.subStates {
if subState.key == key {
rootState.nextState = subState
break
}
}
// 找到切換后的當(dāng)前狀態(tài)
let newCurrentState = findCurrentStateInTree(rootState)
if newCurrentState != currentState {
currentState = newCurrentState
updateStatusBar()
}
printAllStates()
}
/// 刪除某個(gè)狀態(tài)下的子狀態(tài)樹
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()
}
/// 負(fù)責(zé)打印狀態(tài)樹結(jié)構(gòu)
func printAllStates(_ method: String = #function) {
debugPrint("\(method): currentState = \(currentState.key)")
printAllStatesInTree(rootState, deep: 0, method: method)
}
/// 更新棧中 key 對(duì)應(yīng)的狀態(tài)馋劈,key == nil 表示棧頂狀態(tài)
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 表示更新對(duì)應(yīng) key 的狀態(tài),需要判斷該狀態(tài)是否是當(dāng)前狀態(tài)
if let key = key {
guard let currentState = currentState, currentState.key == key else { return }
}
// 狀態(tài)有變化才需要更新視圖
if needUpdate {
updateStatusBar()
}
}
/// 開始更新狀態(tài)欄的狀態(tài)
fileprivate func updateStatusBar() {
DispatchQueue.main.async { // 在主線程異步執(zhí)行 避免同時(shí)索取同一屬性
// 如果狀態(tài)欄需要?jiǎng)赢嫞╢ade or slide)晾嘶,需要添加動(dòng)畫時(shí)間妓雾,才會(huì)有動(dòng)畫效果
UIView.animate(withDuration: self.duration, animations: {
UIApplication.shared.keyWindow?.rootViewController?.setNeedsStatusBarAppearanceUpdate()
})
}
}
/// 從狀態(tài)樹中找到對(duì)應(yīng)的節(jié)點(diǎn)狀態(tài),沒找到就返回根節(jié)點(diǎn)
fileprivate func findState(_ key: String? = nil) -> StatusBarState {
if let key = key { // 查找
if let findState = findStateInTree(rootState, key: key) {
return findState
}
}
return rootState
}
/// 從狀態(tài)樹中找到對(duì)應(yīng)的節(jié)點(diǎn)狀態(tài)的遞歸方法
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
}
/// 刪除某個(gè)狀態(tài)下的所有子狀態(tài)的遞歸方法
fileprivate func removeSubStatesInTree(_ state: StatusBarState) {
state.subStates.forEach { (subState) in
stateKeys.remove(subState.key)
removeSubStatesInTree(subState)
}
state.subStates.removeAll()
}
/// 找到某個(gè)狀態(tài)下的最底層狀態(tài)
fileprivate func findCurrentStateInTree(_ state: StatusBarState) -> StatusBarState? {
if let nextState = state.nextState {
return findCurrentStateInTree(nextState)
}
return state
}
/// 打印狀態(tài)樹結(jié)構(gòu)的遞歸方法
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)
}
}
}
創(chuàng)建 UIViewController+StatusBar 分類和基類控制器來輔助設(shè)置垒迂,簡(jiǎn)單管理狀態(tài)欄:
/// UIViewController+StatusBar.swift
import UIKit
extension UIViewController {
/// 控制器的狀態(tài)欄唯一鍵
var statusBarKey: String {
return "\(self)"
}
/// 設(shè)置該控制器的狀態(tài)欄狀態(tài)
func setStatusBar(isHidden: Bool? = nil, style: UIStatusBarStyle? = nil, animation: UIStatusBarAnimation? = nil) {
StatusBarManager.shared.setState(for: statusBarKey, isHidden: isHidden, style: style, animation: animation)
}
/// 添加一個(gè)子狀態(tài)
func addSubStatusBar(for viewController: UIViewController) {
let superKey = self.statusBarKey
let subKey = viewController.statusBarKey
StatusBarManager.shared.addSubState(with: subKey, root: superKey)
}
/// 批量添加子狀態(tài)君珠,樹橫向生長(zhǎng)
func addSubStatusBars(for viewControllers: [UIViewController]) {
viewControllers.forEach { (viewController) in
self.addSubStatusBar(for: viewController)
}
}
/// 從整個(gè)狀態(tài)樹上刪除當(dāng)前狀態(tài)
func removeFromSuperStatusBar() {
let key = self.statusBarKey
StatusBarManager.shared.removeState(with: key)
}
/// 設(shè)置當(dāng)前狀態(tài)下的所有子狀態(tài)
func setSubStatusBars(for viewControllers: [UIViewController]?) {
clearSubStatusBars()
if let viewControllers = viewControllers {
addSubStatusBars(for: viewControllers)
}
}
/// 通過類似壓棧的形式,壓入一組狀態(tài)娇斑,樹縱向生長(zhǎng)
func pushStatusBars(for viewControllers: [UIViewController]) {
var lastViewController: UIViewController? = self
viewControllers.forEach { (viewController) in
if let superController = lastViewController {
superController.addSubStatusBar(for: viewController)
lastViewController = viewController
}
}
}
/// 切換多個(gè)子狀態(tài)的某個(gè)子狀態(tài)
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)
}
/// 清除所有子狀態(tài)
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)
}
}
基于控制器的全局狀態(tài)欄使用:
class ViewController: BasicViewController {
override func viewDidLoad() {
super.viewDidLoad()
setStatusBar(isHidden: false, style: .default)
}
}
Demo 源碼在這里:StatusBarManagerDemo
有什么問題可以在下方評(píng)論區(qū)提出,寫得不好可以提出你的意見材部,我會(huì)合理采納的毫缆,O(∩_∩)O哈哈~,求關(guān)注求贊