I have a UIViewController with a childViewController.
The childViewController either takes up the entire screen or parts of the screens, overlaying on top of the parentViewController.
When it takes up the entire screen I would like to change the UIStatusBarStyle.
In my plist, I have added View controller-based status bar appearance and set it to NO.
In the childViewController I have the following:
-(UIStatusBarStyle)preferredStatusBarStyle {
if (self.isFullScreen) {
return UIStatusBarStyleDefault;
} else {
return UIStatusBarStyleLightContent;
}
}
-(UIStatusBarAnimation)preferredStatusBarUpdateAnimation {
return UIStatusBarAnimationFade;
}
When making the transition from half and fullscreen I call:
[self setNeedsStatusBarAppearanceUpdate];
But my UIStatusBar does not change appearance from light to dark.
A regular call to:
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent animated:YES];
works fine, however but I would like to take advantage of the fact that I can match the animation duration for the change inside an animation block.
Thank you.
You should set the UIViewControllerBasedStatusBarAppearance to YES in the plist for using -(UIStatusBarStyle)preferredStatusBarStyle method.
Related
I am trying to change my statusbar style (the color of status bar text, more specifically) depending on which viewController is active through this:
self.navigationController.navigationBar.barStyle = UIBarStyleBlack;
But that does not work. Rather, it makes the whole navigationBar black (instead of just the statusbar). Neither does the following:
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
But this only seems to work when the viewController is not embedded in a navController (when I hide the navBar, it works!). My viewController hierarchy is the following:
tabBarController -> navigationControllers -> viewController
Also: Setting 'View controller-based status bar appearance' to YES & NO does not make a difference.
I am glad for any help!
You can try following.
keep this to your ViewController
-(UIStatusBarStyle)preferredStatusBarStyle{
// Add If/else conditions based on which style required on which condition
return UIStatusBarStyleLightContent;
}
Call this code when you want to change the status bar style..
[self preferredStatusBarStyle];
[self setNeedsStatusBarAppearanceUpdate];
I need to let a specific ViewController embedded in an UINavigationController to have light status bar text color (but other ViewControllers to behave differently). I am aware of at least 3 methods, none of which however work in my case.
How to change Status Bar text color in iOS 7, the method is primarily:
Set the UIViewControllerBasedStatusBarAppearance to YES in the plist
In viewDidLoad do a [self setNeedsStatusBarAppearanceUpdate];
Add the following method:
- (UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
Running on iOS 7.0.3, this method does not work for me, since even after I have implemented all 3 steps correctly, preferredStatusBarStyle is never called.
UIStatusBarStyle PreferredStatusBarStyle does not work on iOS 7, the method is primarily:
Setting your navigationBar’s barStyle to UIBarStyleBlackTranslucent will give white status bar text (ie. UIStatusBarStyleLightContent), and UIBarStyleDefault will give black status bar text (ie. UIStatusBarStyleDefault).
This method works fair and square on iPhone, but not on iPad.
Setting the UIViewControllerBasedStatusBarAppearance to NO in the plist, and use
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
This clearly doesn't apply in this case, since I need to only specify different status bar colors for two of the ViewControllers.
Thanks for all help!
For people having this problem with a UINavigationController I can recommend creating a custom UINavigationController and implementing the preferredStatusBarStyle on it like this:
- (UIStatusBarStyle)preferredStatusBarStyle
{
return [self.topViewController preferredStatusBarStyle];
}
That way the statusbar style will be that of the top view controller. Now you can implement the view controller's preferredStatusBarStyle anyway you like.
Here's an improvement to Groot answer, in form of a simple category to UINavigationController, without the need to subclass UINavigationController.
Swift
extension UINavigationController {
override public func preferredStatusBarStyle() -> UIStatusBarStyle {
return topViewController?.preferredStatusBarStyle() ?? .Default
}
}
Swift 3 & Swift 4
extension UINavigationController {
open override var preferredStatusBarStyle: UIStatusBarStyle {
return topViewController?.preferredStatusBarStyle ?? .default
}
}
Objective-C
#implementation UINavigationController (StatusBarStyle)
- (UIStatusBarStyle)preferredStatusBarStyle
{
return [self.topViewController preferredStatusBarStyle];
}
#end
To set UIStatusBarStyle individually for each UIViewController on UINavigationController stack you have to first subclass your UINavigationController and override childViewControllerForStatusBarStyle method.
In your UINavigationController subclass add:
-(UIViewController *)childViewControllerForStatusBarStyle {
return self.visibleViewController;
}
than you can set UIStatusBarStyle to whatever you want in every UIViewController using preferredStatusBarStyle method. Eg:
-(UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
iOS 13 Solution(s)
Regarding your attempt #3 - DEPRECATED
UIApplication.setStatusBarStyle(_:animated:) has been deprecated since iOS 9. According to Apple,
In iOS 7 and later, status bar behavior is determined by view controllers, and so calling this method has no effect by default. When view controller-based status bar appearance is disabled, this method behaves normally. To opt out of the view controller-based status bar appearance behavior, you must add the UIViewControllerBasedStatusBarAppearance key with a value of false to your app’s Info.plist file, but doing so is not recommended.
Regarding your attempt #2 - LEGACY
Setting the barStyle property is now (iOS 13+) considered a "legacy customization." According to Apple,
In iOS 13 and later, customize your navigation bar using the standardAppearance, compactAppearance, and scrollEdgeAppearance properties. You may continue to use these legacy accessors to customize your navigation bar's appearance directly, but you must update the appearance for different bar configurations yourself.
Regarding your attempt #1 - You were on the right track!
UINavigationController is a subclass of UIViewController (who knew 🙃)!
Therefore, when presenting view controllers embedded in navigation controllers, you're not really presenting the embedded view controllers; you're presenting the navigation controllers! UINavigationController, as a subclass of UIViewController, inherits preferredStatusBarStyle and childForStatusBarStyle, which you can set as desired.
Any of the following methods should work:
Override preferredStatusBarStyle within UINavigationController
preferredStatusBarStyle (doc) - The preferred status bar style for the view controller
Subclass or extend UINavigationController
class MyNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
}
OR
extension UINavigationController {
open override var preferredStatusBarStyle: UIStatusBarStyle {
.lightContent
}
}
Override childForStatusBarStyle within UINavigationController
childForStatusBarStyle (doc) - Called when the system needs the view controller to use for determining status bar style
According to Apple's documentation,
"If your container view controller derives its status bar style from one of its child view controllers, [override this property] and return that child view controller. If you return nil or do not override this method, the status bar style for self is used. If the return value from this method changes, call the setNeedsStatusBarAppearanceUpdate() method."
In other words, if you don't implement solution 3 here, the system will fall back to solution 2 above.
Subclass or extend UINavigationController
class MyNavigationController: UINavigationController {
override var childForStatusBarStyle: UIViewController? {
topViewController
}
}
OR
extension UINavigationController {
open override var childForStatusBarStyle: UIViewController? {
topViewController
}
}
You can return any view controller you'd like above. I recommend one of the following:
topViewController (of UINavigationController) (doc) - The view controller at the top of the navigation stack
visibleViewController (of UINavigationController) (doc) - The view controller associated with the currently visible view in the navigation interface (hint: this can include "a view controller that was presented modally on top of the navigation controller itself")
Note: If you decide to subclass UINavigationController, remember to apply that class to your nav controllers through the identity inspector in IB.
P.S. My code uses Swift 5.1 syntax 😎
I used the first method you mentioned, I also found there's kinda bug when you used UINavigationController, it will never pass preferredStatusBarStyle call to it's child view controllers. What I have done is subclass the UINavigationController, and override preferredStatusBarStyle method as follows:
#implementation GLBaseNavigationController
- (UIStatusBarStyle)preferredStatusBarStyle
{
UIViewController *lastViewController = [self.viewControllers lastObject];
if ([lastViewController respondsToSelector:#selector(preferredStatusBarStyle)]) {
return [lastViewController preferredStatusBarStyle];
} else if ([super respondsToSelector:#selector(preferredStatusBarStyle)]) {
return [super preferredStatusBarStyle];
}
return UIStatusBarStyleDefault;
}
Then whenever I need a navigation controller, I use GLBaseNavigationController instead of UINavigationController. For storyboards, you need to specify the class of the navigation controller to your subclass as well.
For your first solution, I don't think you can change the status bar in viewDidLoad. If you have two ViewControllers stacked on top of each other, and each one toggles the status bar differently, that method will only get called once for each. You really want to change the status bar in viewWillAppear so that it gets called each time the page is shown. I also don't think you can rely on preferredStatusBarStyle since I'm also not sure how often/when that gets called. This is how you want to do it:
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController.navigationBar setBarStyle:UIBarStyleDefault];
}
Currently you can only do light and dark. To change to light do.
Set the UIViewControllerBasedStatusBarAppearance to YES in the .plist file.
In the viewDidLoad method do [self setNeedsStatusBarAppearanceUpdate];
Add the this method:
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
To change it back to dark change the UIStatusBarStyleLightContent to UIStatusBarStyleDefault
In your AppDelegate didFinishLaunch method, set the default status bar style, say:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault
animated:YES];
return YES;
}
Then, in your those two view controllers, where you want to change status bar, override following methods:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated]
// Here change status bar color
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent
animated:YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear]
// Here bring back to color, that we set in AppDelegate
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault
animated:YES];
}
I am trying to change the status bar style of one of my viewcontrollers. I have put this
set the view based status bar to YES in the plist
2.
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}
Added this also
[self setNeedsStatusBarAppearanceUpdate]
It works i.e I can see the font color white but just after some time it changes back to its previous type..
If you are experiencing status bar changing color itself during runtime
try setting set the UIViewControllerBasedStatusBarAppearance to NO in the plist.
And inside your viewController.. set the appearance call inside
-(void)viewDidLayoutSubviews
{
if ([self respondsToSelector:#selector(setNeedsStatusBarAppearanceUpdate)]) {
[self setNeedsStatusBarAppearanceUpdate];
}
}
write following code
-(void)viewWillAppear:(BOOL)Animated{
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
}
This is the only thing I could get working for iOS7
- (UIStatusBarStyle)preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
You can check this code, a little trick – but useful sometimes.
The iOS 7 Transition Guide give a good hint how to change the UIStatusBarStyle dynamically in a UIViewController using
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleDefault;
}
together with [self setNeedsStatusBarAppearanceUpdate];
This works fine in a single view application. However, I'm now trying to change the UIStatusBarStyle in a modal view to UIStatusBarStyleLightContent. There is a MainViewController which segues to the ModalViewController, which itself is embedded in a NavigationController. The ModalViewController has set its delegate to the MainViewController.
I tried to call [self setNeedsStatusBarAppearanceUpdate]; in the ModalViewController together with the following method in that class without effect:
// In ModalViewController.m
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
I also tried to call [self setNeedsStatusBarAppearanceUpdate]; in MainViewController on prepareForSegue: sender: method with conditions in - (UIStatusBarStyle)preferredStatusBarStyle {} to return UIStatusBarStyleLightContent when the modal view is presented - but that has no effects, too.
How can I change the UIStatusBarStyle in the modal view?
EDIT: Post updated: I need to mention that the ModalViewController is embedded in a NavigationController with a NavigationBar. With NavigationBar set to hidden to above call of [self setNeedsStatusBarAppearanceUpdate]; in ModalViewController works fine. But not when the Bar is visible.
You need a ViewController that's showing in Fullscreen to return the appropriate status bar infos. In your case: The NavigationController which contains ModalViewController needs to implement preferredStatusBarStyle and return UIStatusBarStyleLightContent.
A call to setNeedsStatusBarAppearanceUpdate is only necessary if the values a view controller returns actually change. When the view controller is first presented they are queried anyway.
We should notice that non-fullscreen modalVC CAN use modalPresentationCapturesStatusBarAppearance to control the statusBar style.
Anyone who wanna know more about Status Bar control should not ignore the UIViewController Managing the Status Bar.
Update at 2015-11-06:
And make sure you have set UIViewControllerBasedStatusBarAppearance described in iOS Keys
Update at 2018.04.09:
I noticed that viewController in a navController may not get call prefersStatusBarHidden with iOS 10.0 - 10.2. Custom your navigationController to ensure that
#implementation YourCustomNavController
//for iOS 10.0 - iOS 10.2
- (BOOL)prefersStatusBarHidden {
UIViewController *childVC = [self childViewControllerForStatusBarHidden];
if (childVC) {
return [childVC prefersStatusBarHidden];
}
return [super prefersStatusBarHidden];
}
#end
And anyone who want to go deeper inside can dig into UIKit +[UIViewController _currentStatusBarStyleViewController] using Hopper or IDA Pro. It may helps you solve these kinds of bugs.
The key to making this work is that only the fullscreen view controller get's to dictate the style of the status bar.
If you are using a navigation controller and want to control the status bar on a per view controller basis, you'll want to subclass UINavigationController and implement preferredStatusBarStyle such that it returns the topViewController's preference.
Make sure you change the class reference in your storyboard scene fromUINavigationController to your subclass (e.g. MyNavigationController in the example below).
(The following works for me. If your app is TabBar based, you'll want to do something similar by subclassing the UITabBarController but I haven't tried that out).
#interface MyNavigationController : UINavigationController
#end
#implementation MyNavigationController
- (UIStatusBarStyle)preferredStatusBarStyle
{
return self.topViewController.preferredStatusBarStyle;
}
#end
To change the status bar of the UINavigationController embedding your ViewController without subclassing UINavigationController, use this:
navigationController?.navigationBar.barStyle = .Black // to make the status bar text white
.Black will make the text white (status bar and the view's title), while .Default has a black title and status bar.
I had a side menu/reveal controller (SWRevealController) which turns out to always be the root controller for status bar queries. Overriding childViewControllerForStatusBarStyle let me re-route the query to the front most controller.
/**
This view is always considered the topmost for status bar color queries.
Pass the query along to what we're showing in front.
*/
- (UIViewController *)childViewControllerForStatusBarStyle
{
UIViewController *front = self.frontViewController;
if ([front isKindOfClass:[UINavigationController class]])
return ((UINavigationController*)front).topViewController;
else
return front;
}
It seems like the app goes off the statusBarStyle of the topmost viewController. So if you add another viewController on top of your current one, it now gets its cues from the new viewController.
This works for me:
Set View controller-based status bar appearance to NO
Set Status bar style to UIStatusBarStyleLightContent (just copy that value)
In appDelegate use [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
Hope it helps (ref: ios7 status bar changing back to black on modal views?)
Just look up if your app's rootViewController need to override -(UIStatusBarStyle)preferredStatusBarStyle method
All of the above work. However sometimes I find it really a pain in the bottom to go and change every instance in the Storyboard etc... So here's something that works for me that also involves subclassing.
First create the subclass:
#interface HHNavLightColorBarController : UINavigationController
#end
#implementation HHNavLightColorBarController
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
#end
Then using the magic of Objective-C and a little bit of the <objc/runtime.h>
When you have a reference of the view controller and your presenting it:
UINavigationController *navVC = ...; // Init in your usual way
object_setClass(navVC, [HHNavLightColorBarController class]);
[self presentViewController:nav animated:YES completion:^{
NSLog(#"Launch Modal View Controller");
}];
Sometimes it seems a bit less intrusive. You could probably even create a category that checks to see if your kindOfClass is a navigation controller and auto do it for you. Anyways, the answer is above by jaetzold, I just found this to be handy.
Is it possible to have the status bar hidden in just one scene in the Storyboard?
For example:
I want it hidden in "ViewController A", but when moving via a segue to "ViewController B" (eg, a Navigation Controller) the status bar will be shown.
I gather you can disable it throughout the app, but how do you do it for just one ViewController?
The suggestion from below does work, however when switching to the Navigation Controller it seems to cause the nav bar to be drawn in the wrong location.
How about
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[[UIApplication sharedApplication] setStatusBarHidden:YES];
}
- (void)viewWillDisappear:(BOOL)animated{
[[UIApplication sharedApplication] setStatusBarHidden:NO];
[super viewWillDisappear:animated];
}
Just put the code on your ViewController.m (in iOS7, Xcode 5)
- (BOOL) prefersStatusBarHidden {
return YES;
}
My solution: In the storyboard, select your Scene and set Status Bar: None in the Attributes Inspector.
For Swift 4:
override var prefersStatusBarHidden: Bool {
return true
}