背景
iOS 中經常會有需要在某個界面改變狀態欄顏色或者某個界面隱藏狀態欄的需求。而改變狀態欄顏色和控制狀態欄顯示和隱藏的API,在iOS 的不同版本中也發生了很多變化。
iOS 7以前
在iOS 7之前,狀態欄是不占視圖位置的。每個控制器中的根view都是從屏幕的Y軸20px處開始顯示的。所以那個時候整個app狀態欄的風格,一般只在plist文件裡設置【對應於General中的Status Bar Style】。印象裡似乎只有黑白兩種風格,已記不清了!
iOS 7以前狀態欄設置
從API來看,那時候也是支持在代碼裡修改狀態欄的樣式以及顯示和隱藏的。只是因為狀態欄對整個APP的影響不大,所以一般在plist裡設置好後,用不著再去修改了。
API
iOS 7 ~iOS 9
從iOS 7開始系統風格大變樣,圖標扁平了,狀態欄也不在鬧獨立了。因為狀態欄的會受到導航欄或者View背景色的影響,所以狀態欄的風格也需要實時調整了。
想要改變狀態欄的樣式,想要控制狀態欄的顯示與隱藏,該怎麼做呢?
1. 用UIApplication的API
首先,需要在plist文件裡將【View controller-based status bar appearance】設置為NO,因為它的默認值是YES,然後就可以利用UIApplication 來設置了。
plist設置
先上效果動畫:
再上源碼:
- (IBAction)changeStatus:(UISegmentedControl *)sender { if (sender.selectedSegmentIndex == 0) { // [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault]; // 帶動畫效果,動畫效果其實就是變換的時間變慢了 [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault animated:YES]; } else { // [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent]; // 帶動畫效果,動畫效果其實就是變換的時間變慢了 [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:YES]; } } - (IBAction)showOrHidden:(UISegmentedControl *)sender { if (sender.selectedSegmentIndex == 0) { // 第二個參數是個枚舉類型 [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide]; // 不帶動畫效果 // [[UIApplication sharedApplication] setStatusBarHidden:NO]; } else { [[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationSlide]; // 不帶動畫效果 // [[UIApplication sharedApplication] setStatusBarHidden:YES]; } }
2. 重寫ViewController方法
首先,要確保plist文件中【View controller-based status bar appearance】為YES,沒有添加這個key的時候,默認是YES。
plist設置
然後在視圖控制器中,重寫如下三個方法即可:
要重寫的方法
因為這三個方法都有默認值,如果我們要的狀體欄樣式什麼的跟默認值效果一致,則不需要重寫;如果不想要默認的效果,則直接在這三個方法裡return 相應的值即可。你不必三個方法都重寫,看實際情況。例如,我想要在這個界面時狀態欄為白色,狀態欄不隱藏,那麼我只用重寫-preferredStatusBarStyle,like this:
- (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleLightContent; }
因為我這裡需要做一個切換所以,我首先定義了兩個property:
@property (assign, nonatomic) UIStatusBarStyle statusBarStyle; /**< 狀態欄樣式 */ @property (assign, nonatomic) BOOL statusBarHidden; /**< 狀態欄隱藏 */
然後改變UISegmentedControl的值時,在響應的Action方法裡改變上述property的值,再調用
-setNeedsStatusBarAppearanceUpdate即可。
示例代碼:
#pragma mark - ViewController方式 - (IBAction)changeStyle:(UISegmentedControl *)sender { if (sender.selectedSegmentIndex == 0) { _statusBarStyle = UIStatusBarStyleDefault; } else { _statusBarStyle = UIStatusBarStyleLightContent; } [self setNeedsStatusBarAppearanceUpdate]; } - (IBAction)statusShowOrHidden:(UISegmentedControl *)sender { if (sender.selectedSegmentIndex == 0) { _statusBarHidden = NO; } else { _statusBarHidden = YES; } [self setNeedsStatusBarAppearanceUpdate]; } #pragma mark - 需要重寫的幾個狀態欄方法 /** * 控制狀態欄的樣式 * 要刷新狀態欄,讓其重新執行該方法需要調用{-setNeedsStatusBarAppearanceUpdate} * * @return 將要顯示的狀態欄樣式 */ - (UIStatusBarStyle)preferredStatusBarStyle { return _statusBarStyle; } /** * 狀態欄顯示還是隱藏 * 要刷新狀態欄,讓其重新執行該方法需要調用{-setNeedsStatusBarAppearanceUpdate} * * @return BOOL值 */ - (BOOL)prefersStatusBarHidden { return _statusBarHidden; } /** * 狀態欄改變的動畫,這個動畫只影響狀態欄的顯示和隱藏 * * @return 動畫效果 */ - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { return UIStatusBarAnimationSlide; }
iOS 9 之後
如上面第二張圖所示,UIApplication的控制狀態欄的方法,在iOS 9之後被棄用了。
所以iOS 9之後盡量使用重寫ViewController方法的方式吧。
注意點
情形一
如果我們使用UINavigationController,會發現在原來的ViewController裡修改狀態欄的style不起作用了,但是控制狀態欄的顯示和隱藏依然OK。但是使用UITabBarController依然正常,狀態欄不受UITabBarController影響。
重寫UINavigationController的三個方法:
- (UIStatusBarStyle)preferredStatusBarStyle { NSLog(@"導航欄-%s",__func__); return [self.topViewController preferredStatusBarStyle]; } - (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { NSLog(@"導航欄-%s",__func__); return UIStatusBarAnimationNone; } - (BOOL)prefersStatusBarHidden { NSLog(@"導航欄-%s",__func__); return NO; }
從打印結果:
2016-05-18 13:18:10.248 PractiseProject[3296:112707] 導航欄--[BaseNavigationController preferredStatusBarStyle] 2016-05-18 13:18:10.249 PractiseProject[3296:112707] -[ViewController prefersStatusBarHidden] 2016-05-18 13:18:10.275 PractiseProject[3296:112707] 導航欄--[BaseNavigationController preferredStatusBarStyle] 2016-05-18 13:18:10.275 PractiseProject[3296:112707] -[ViewController prefersStatusBarHidden] 2016-05-18 13:18:10.275 PractiseProject[3296:112707] 導航欄--[BaseNavigationController preferredStatusBarStyle] 2016-05-18 13:18:10.276 PractiseProject[3296:112707] -[ViewController prefersStatusBarHidden]
可以看出,只調用了第一個方法。所以我們只需要重寫UINavigaitonController的- preferredStatusBarStyle即可。
情形二
狀態欄的樣式、是否顯示實際上是由頂層window的當前視圖控制器決定的。
比如我們在程序入口處創建一個新的window:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. self.statusWindow = [[UIWindow alloc] initWithFrame:application.statusBarFrame]; // 這裡設置windowLevel 為UIWindowLevelStatusBar或者UIWindowLevelAlert都可以 self.statusWindow.windowLevel = UIWindowLevelStatusBar; // 顏色必須為clearColor,否則會蓋住狀態欄的區域 self.statusWindow.backgroundColor = [UIColor clearColor]; self.statusWindow.rootViewController = [[StatusViewContrller alloc] init]; self.statusWindow.hidden = NO; return YES; }
然後在StatusViewContrller中重寫如下方法:
- (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleLightContent; } // 如果想要顯示狀態欄,必須重寫這個方法,並return NO - (BOOL)prefersStatusBarHidden { return NO; }
這樣最終狀態欄的樣式就由StatusViewContrller決定了,而不是由原來的ViewController決定了。
創建頂層window之後,修改狀態欄的樣式就不方便了。
為了解決這個問題,我們可以將StatusViewContrller弄成單例,然後定義兩個property來控制樣式和是否隱藏即可。
#import@interface StatusViewContrller : UIViewController @property (assign, nonatomic) UIStatusBarStyle statusBarStyle; /**< 狀態欄樣式 */ @property (assign, nonatomic) BOOL statusBarHidden; /**< 狀態欄隱藏 */ + (instancetype)sharedInstance; @end
重寫兩個property的set方法,設置完屬性後調用狀態欄刷新方法:
// 創建單例的關鍵代碼 static id instance = nil; + (instancetype)sharedInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[self alloc] init]; }); return instance; } // 這個方法是關鍵 + (instancetype)allocWithZone:(struct _NSZone *)zone { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [super allocWithZone:zone]; }); return instance; } // 重寫的方法 - (UIStatusBarStyle)preferredStatusBarStyle { return _statusBarStyle; } - (BOOL)prefersStatusBarHidden { return _statusBarHidden; } // setter - (void)setStatusBarStyle:(UIStatusBarStyle)statusBarStyle { _statusBarStyle = statusBarStyle; [self setNeedsStatusBarAppearanceUpdate]; } - (void)setStatusBarHidden:(BOOL)statusBarHidden { _statusBarHidden = statusBarHidden; [self setNeedsStatusBarAppearanceUpdate]; }
創建了頂層window後,唯一需要注意的是頂層window和其根視圖控制器的背景色必須為clearColor。
That's all。Have Fun!