iOS 填坑系列 - 狀態欄變化

概述

相信很多iOS開發者都做過改變狀態欄樣式和隱藏狀態欄,這個功能也挺簡單的,但應該也有不少人踩過其中的坑。蘋果特別喜歡動不動就改,每個版本都不一樣,這個方法這個版本好好的,下個版本就非得用另外一種方式實現才行。而蘋果對于狀態欄修改的方式一直折騰不休,作為一個經歷了從iOS 7 到 iOS 10開發者,在這方面也一直被困擾著,一直不知道到底哪種方式才是正確的,每次都要試一試才敢確定。踩了太多坑,于是在這里總結一下吧。

本文是基于 iOS 10的,有些方面對比了 iOS 9 和 iOS 10 的異同,其他不同版本請Google。

隱藏和修改樣式

iOS 9 和 10 中,默認情況下,狀態欄的改變是通過 view-controlls 控制的,由每個控制器決定狀態欄的樣式以及是否隱藏狀態欄。

只需在控制器中重載 prefersStatusBarHiddenpreferredStatusBarStyle就可以控制是否隱藏狀態欄以及修改狀態欄的樣式。

// 隱藏狀態欄
override var prefersStatusBarHidden: Bool {
    return true
}

// 修改狀態欄的樣式為白色
override var preferredStatusBarStyle: UIStatusBarStyle {
    return .lightContent
}

全局控制狀態欄

iOS 9 中,全局控制狀態欄的改變,只需兩步:

  1. Info.plist中,添加屬性 View controller-based status bar appearance,設置為 NO
  2. 添加如下代碼:
// 修改狀態欄的樣式為白色
UIApplication.shared.setStatusBarStyle(.lightContent, animated: true)

// 隱藏狀態欄
UIApplication.shared.setStatusBarHidden(true, with: .fade)

注意:如果不設置View controller-based status bar appearance或設置為 YES,則上面兩句代碼不生效。

iOS 10 中,上述兩個方法已經被棄用了,蘋果推薦使用在控制器中重載prefersStatusBarHiddenpreferredStatusBarStyle的方法來控制狀態欄的改變。但是如果你硬是要使用的話,還是會有效果的,只不過會看到兩個警告罷了。

'setStatusBarStyle(_:animated:)' was deprecated in iOS 9.0: Use -[UIViewController preferredStatusBarStyle]
'setStatusBarHidden(_:with:)' was deprecated in iOS 9.0: Use -[UIViewController prefersStatusBarHidden]

如果不想看到這兩個警告,在 iOS 10 中,還有另外一種方式可以全局控制,除了設置View controller-based status bar appearanceNO外,需要修改【General】-->【Deployment Info】-->【Status Bar Style】。

改變狀態欄的方向

UIApplication.shared.statusBarOrientation = .portrait

填坑

狀態欄改變不了的情況

在一個控制器中添加一個 ChildViewController ,如果想在 ChildViewController 來控制狀態欄,就會失效。

解決辦法是:重載 childViewControllerForStatusBarHidden方法,并在viewDidLoad中刷新一次狀態欄

class StatusBarHiddenParentController: UIViewController {
    
    var childController: StatusBarHiddenChildController?
    override func viewDidLoad() {
        super.viewDidLoad()
        childController = StatusBarHiddenChildController.fromStoryboard("Main")
        addChildViewController(childController!)
        view.addSubview(childController!.view)
        
        // 刷新狀態欄
        setNeedsStatusBarAppearanceUpdate()
    }
    override func childViewControllerForStatusBarHidden() -> UIViewController? {
        return childController
    }
    
    override func prefersStatusBarHidden() -> Bool {
        return false
    }
}

class StatusBarHiddenChildController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    override func prefersStatusBarHidden() -> Bool {
        return true
    }
}

還有一個需要注意的問題是,當 ChildViewController 從父控制器中移除時,需要再次刷新狀態欄,具體做法請參考我以前的文章 iOS Status Bar 的隱藏

在View中控制狀態欄的改變

我在做 JFPlayer 的時候,封裝了一個播放視頻的View,在播放器從窗口切換為全屏時,需要改變狀態欄的方向以及隱藏狀態欄。因為是要做成一個簡單易用的庫,所以要封裝的更徹底,不能把細節暴露出去,這時就要在View(而不是控制器)中改變狀態欄了,有三個問題需要解決:

  • 項目中的View controller-based status bar appearance的值是YES還是NO,基于這個判斷是由控制器決定狀態欄還是全局控制。
  • 如果是控制器決定狀態欄,那如何獲取該View的父控制器
  • 如果是控制器決定狀態欄,那控制器如何知道此時是否應該隱藏呢
控制狀態欄
extension UIApplication {
    
    func jf_usesViewControllerBasedStatusBarAppearance() -> Bool {
        let key = "UIViewControllerBasedStatusBarAppearance"
        guard  let object = Bundle.main.object(forInfoDictionaryKey: key) else {
            return true
        }
        
        return (object as! Bool)
    }
    
    func jf_updateStatusBarAppearanceHidden(_ hidden: Bool, animation: UIStatusBarAnimation, fromViewController sender: UIViewController) {
        
        if jf_usesViewControllerBasedStatusBarAppearance() {
            sender.setNeedsStatusBarAppearanceUpdate()
        } else {
            UIApplication.shared.setStatusBarHidden(hidden, with: animation)
        }
    }
}

獲取View的父控制器
extension UIView {
    var parentViewController: UIViewController? {
        var parentResponder: UIResponder? = self
        while parentResponder != nil {
            parentResponder = parentResponder!.next
            if let viewController = parentResponder as? UIViewController {
                return viewController
            }
        }
        return nil
    }
}

第三個問題,需要借助一個變量 statusBarIsHidden 標記下此時狀態欄是否該隱藏,并在控制器中重載prefersStatusBarHidden方法進行判斷。

實現代碼如下。

  1. 在點擊全屏按鈕時,改變狀態欄,調用 updateStatusBarAppearanceHidden方法更新狀態欄
func fullScreenButtonPressed(_ button: UIButton?) {
        
    // force change device and status bar orientation, that toggle the UIApplicationDidChangeStatusBarOrientation notification
    if isFullScreen {
        
        UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation")
        updateStatusBarAppearanceHidden(false)
        UIApplication.shared.statusBarOrientation = .portrait
    } else {
        
        UIDevice.current.setValue(UIInterfaceOrientation.landscapeRight.rawValue, forKey: "orientation")
        updateStatusBarAppearanceHidden(false)
        UIApplication.shared.statusBarOrientation = .landscapeRight
    }
}

fileprivate func updateStatusBarAppearanceHidden(_ hidden: Bool) {
    statusBarIsHidden = hidden
    if let parentViewController = self.parentViewController {
        UIApplication.shared.jf_updateStatusBarAppearanceHidden(self.statusBarIsHidden, animation: .none, fromViewController: parentViewController)
    }
}

  1. 在控制器中重載 prefersStatusBarHidden
 override var prefersStatusBarHidden: Bool {
    guard let hidden = player?.statusBarIsHidden else {
        return false
    }
    return hidden
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 正如每個java文檔所描述的那樣,CountDownLatch是一個同步工具類,它允許一個或多個線程一直等待,直到...
    algernoon閱讀 559評論 0 1
  • 午飯過后,我看著《綠》,弟弟看動畫片;叔叔看今日頭條,嬸嬸在清洗碗筷,一切一如往常。 門被打開了,妹妹從學校補習功...
    凡心未了閱讀 274評論 0 0
  • 上初中后才接觸到幻燈片(ppt),那時候最感興趣的莫過于各種淡入淡出、彈出、百葉窗的動畫效果,如果某位同學做了一個...
    大臉貓撿破爛閱讀 997評論 0 0
  • 男人遠比你想像中要脆弱,也許你還沒靠上去,他們就倒了。 早上看了一篇文章,提到女人的兩條出路,一條是讀大學,知識改...
    或者時光閱讀 1,381評論 0 0
  • 今天與許久不聯系的老同桌聊天,海南大學商務英語專業,前不久剛拿到美國加州大學伯克利分校的offer,10號就要去...
    夢中的安妮小姐閱讀 302評論 0 1