UIStatusBarStyle PreferredStatusBarStyle does not work on iOS 7 - iphone

In my iPhone application built with Xcode 5 for iOS 7 I set UIViewControllerBasedStatusBarAppearance=YES in info.plist, and in my ViewController I have this code:
-(UIStatusBarStyle) preferredStatusBarStyle
{
return UIStatusBarStyleLightContent;
}
But the status bar is still black against the black background.
I know its possible to change this app-wide by setting UIViewControllerBasedStatusBarAppearance=NO in info.plist, but I actually need to alter this on a viewController by viewController basis at runtime.

I discovered that if your ViewController is inside a navigationController then the navigationController’s navigationBar.barStyle determines the statusBarStyle.
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).
Note that this applies even if you totally change the navigationBar’s color via its barTintColor.

OK, here's the trick. You do have to add the key "View controller-based status bar" and set the value to No.
This is counter to what it appears the meaning of this key is, but even if you set the value to No, you can still change the appearance of the status bar, and whether it shows or not in any view controller. So it acts like "Yes" but set it to "No"!
Now I can get the status bar white or dark.

For preferredStatusBarStyle() to work within UINavigationController and UITabBarController I add the following code, which will get the preferred status bar style from the currently visible view controller.
extension UITabBarController {
public override func childViewControllerForStatusBarStyle() -> UIViewController? {
return selectedViewController
}
}
extension UINavigationController {
public override func childViewControllerForStatusBarStyle() -> UIViewController? {
return visibleViewController
}
}
For Swift 3 those are not methods but properties:
extension UITabBarController {
open override var childViewControllerForStatusBarStyle: UIViewController? {
return selectedViewController
}
}
extension UINavigationController {
open override var childViewControllerForStatusBarStyle: UIViewController? {
return visibleViewController
}
}
The Swift 4.2 properties have been renamed:
extension UITabBarController {
open override var childForStatusBarStyle: UIViewController? {
return selectedViewController
}
}
extension UINavigationController {
open override var childForStatusBarStyle: UIViewController? {
return visibleViewController
}
}
Usage
class ViewController: UIViewController {
// This will be called every time the ViewController appears
// Works great for pushing & popping
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}

I may be coming to this a bit late, but incase anyone else is looking for a working and verified app wide solution.
#mxcl is correct in describing why this is happening. In order to correct it, we simply create an extension (or category in obj-c) that overrides the preferredSatusBarStyle() method of UINavigationController. Here is an example in Swift:
extension UINavigationController {
public override func preferredStatusBarStyle() -> UIStatusBarStyle {
if let rootViewController = self.viewControllers.first {
return rootViewController.preferredStatusBarStyle()
}
return super.preferredStatusBarStyle()
}
}
This code simply extracts the first view controller (the root view controller) and unwraps it (in obj-c just check that it is not nil). If the unwrap is successful (not nil) then we grab the rootViewControllers preferredStatusBarStyle. Otherwise we just return the default.
Hope this helps anyone who might need it.

To provide more detail into the accepted answer, put the following line in your app delegate's didFinishLaunchingWithOptions: method:
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
Then, in your Info.plist, add View controller-based status bar appearance and set it to NO.
I believe that's how it should be done, NOT from the navigation controller, if you want the same status bar color for the entire app. You might have screens that are not necessarily embedded in a UINavigationController, or a different UINavigationController subclass somewhere else, and other things.
EDIT: You can also do it without typing any code: https://stackoverflow.com/a/18732865/855680

In viewDidLoad just write this
[self setNeedsStatusBarAppearanceUpdate];
just do that and it will work
can u please try this
Set UIViewControllerBasedStatusBarAppearance to NO.
Call [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
One more thing i have seen in your question that
you have wrote the method like this
-(void)UIStatusBarStyle PreferredStatusBarStyle ()
{
return UIStatusBarStyle.LightContent;
}
but it should be like this
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleLightContent;
}

iOS 13 Solution(s)
The highest-voted answer uses "legacy" code 👎
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 - 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 😎

Here is how I solved it. Usually the navigationController or tabBarController are the ones deciding the appearance of the status bar (hidden, color, etc).
So I ended up subclassing the navigation controller and overriding preferredStatusBarStyle. if the current visible ViewContorller implements StatusBarStyleHandler I ask for the value to be used as the style, if it doesn't I just return a default value.
The way you trigger an update of the status bar appearance is by calling setNeedsStatusBarAppearanceUpdate which triggers preferredStatusBarStyle again and updates UI according to what the method returns
public protocol StatusBarStyleHandler {
var preferredStatusBarStyle: UIStatusBarStyle { get }
}
public class CustomNavigationCotnroller: UINavigationController {
public override var preferredStatusBarStyle: UIStatusBarStyle {
if let statusBarHandler = visibleViewController as? StatusBarStyleHandler {
return statusBarHandler.preferredStatusBarStyle
}
return .default
}
}
Then usage
public class SomeController: UIViewController, StatusBarStyleHandler {
private var statusBarToggle = true
// just a sample for toggling the status bar style each time method is called
private func toggleStatusBarColor() {
statusBarToggle = !statusBarToggle
setNeedsStatusBarAppearanceUpdate()
}
public override var preferredStatusBarStyle: UIStatusBarStyle {
return statusBarToggle ? .lightContent : .default
}
}

Even with all the answers here i still didn't find the exact solution for me, but started with the answer from Daniel. What I ended up with was:
override var preferredStatusBarStyle: UIStatusBarStyle {
return visibleViewController?.preferredStatusBarStyle ?? .lightContent
}
in navigation controllers (similar for tab, just selectedViewController). And then it will respect the:
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
In each view controller unless you set it otherwise. I dont need to call setNeedsStatusBarAppearanceUpdate() anywhere, it just updates when you arrive at each view controller.

1) One setting for whole project:
If available, remove UIViewControllerBasedStatusBarAppearance key-value pair from your info.plist, or set NO without removing it. If it's not available in your info.plist, do nothing. Default is NO for this property.
Add below code to your AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
}
2) Different settings for different View Controllers:
Add UIViewControllerBasedStatusBarAppearance key-value pair to your info.plist and set it to YES.
If your View Controller is not embed in to Navigation Controller. Let's say MyViewController. just add code below to your MyViewController.m file. If your View Controller is embed in to Navigation Controller, create a new Cocoa Touch Class and make it subclass of UINavigationController. Let's say MyNC. Select Navigation Controller View on your Storyboard, at right pane; Utilities -> Identity Inspector -> Custom Class -> Class, type "MyNC". After linking Storyboard View with your "MyNC" Cocoa Touch Class, add code below to your MyNC.m:
- (BOOL)prefersStatusBarHidden {
return NO;
}
-(UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}

If in case you wanted to hide the statusBar during splashScreen but wanted to change the style to light content (StatusBarInitiallyHidden on Plist has to be NO to hide statusBar on splash), you can add this to appDelegate's didFinishLaunchingWithOptions method to change to lightContent.
[[UIApplication sharedApplication]setStatusBarHidden:NO withAnimation:UIStatusBarAnimationSlide];
[[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent];

swift example
in AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent;
return true
}
in info.plist set View controller-based status bar appearance: NO

If you're using NavigationController, you can subclass NavigationController so that it consults its child view controller
// MyCustomNavigationController
- (NSUInteger)supportedInterfaceOrientations {
UIViewController *viewControllerToAsk = [self findChildVC];
return [viewControllerToAsk supportedInterfaceOrientations];
}
- (BOOL)shouldAutorotate {
UIViewController *viewControllerToAsk = [self findChildVC];
return [viewControllerToAsk shouldAutorotate];
}
- (UIStatusBarStyle)preferredStatusBarStyle {
UIViewController *viewControllerToAsk = [self findChildVC];
return [viewControllerToAsk preferredStatusBarStyle];
}
- (UIViewController *)findChildVC {
return self.viewControllers.firstObject;
}

Swift 4.2
extension UITabBarController {
open override var childForStatusBarStyle: UIViewController? {
return selectedViewController
}
}
extension UINavigationController {
open override var childForStatusBarStyle: UIViewController? {
return visibleViewController
}
}

You can set the status bar style. It will resembles the status bar like IOS 6 and below.
Paste this methods in your view controller
-(UIStatusBarStyle)preferredStatusBarStyle{
return UIStatusBarStyleBlackOpaque;
}
and call this method from view did load like this
if([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0f)
{
[self setNeedsStatusBarAppearanceUpdate];
}

swift example
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: NSDictionary?) -> Bool {
UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent;
return true
}
in info.plist set View controller-based status bar appearance: NO

I just want to add a note for a specific case I faced. I had another UIWindow in my app to display a chat face to be floating all over my app all the time. Doing this caused none of the solution above to work, and I am not really sure why! All what I have noticed is that my ViewController in the new UIWindow was the reason for that! And if I wanted to change the status bar style I have to do it in that view controller of the new UIWindow.
This note might help others who have a similar structure! So basically you can apply the solutions mentioned above in the ViewController of the new UIWindow.
Again this a specific case.
Thanks

Wanna some tricky? No needs to override status bar style in every view controller
First: Follow #Sahil Kapoor, add 'View controller-based status bar = YES' to plist
Second: Make a subclass of window's root view controller and return StatusBarTrackingController.
final class StatusBarTracker: UIViewController {
override var preferredStatusBarStyle: UIStatusBarStyle {
CustomThemeProvider.currentTheme.asStatusBarStyle
}
}
final class TabBarController: UITabBarController {
private let statusBarTracker = StatusBarTracker()
override var childForStatusBarStyle: UIViewController? {
statusBarTracker
}
}
extension TabBarController: CustomThemeUpdatable {
func applyCustomTheme(_ theme: CustomTheme) {
setNeedsStatusBarAppearanceUpdate()
}
}
// SceneDelegate
window?.rootViewController = TabBarController()

For swift 3, in your UIViewController:
override var preferredStatusBarStyle : UIStatusBarStyle { return UIStatusBarStyle.lightContent }

Related

StatusBar text color doesn't change to Light Content [duplicate]

Adding
application.statusBarStyle = .lightContent
to my AppDelegate's didFinishLaunchingWithOptions method nor adding
override var preferredStatusBarStyle: UIStatusBarStyle {
return UIStatusBarStyle.lightContent
}
to the VC no longer works on iOS12/Xcode10
Any ideas?
This has nothing to do with iOS 12. You just have the rules wrong.
In a navigation controller situation, the color of the status bar is not determined by the view controller’s preferredStatusBarStyle.
It is determined, amazingly, by the navigation bar’s barStyle. To get light status bar text, say (in your view controller):
self.navigationController?.navigationBar.barStyle = .black
Hard to believe, but true. I got this info directly from Apple, years ago.
You can also perform this setting in the storyboard.
Example! Navigation bar's bar style is .default:
Navigation bar's bar style is .black:
NOTE for iOS 13 This still works in iOS 13 as long as you don't use large titles or UIBarAppearance. But basically you are supposed to stop doing this and let the status bar color be automatic with respect to the user's choice of light or dark mode.
If you choose a same status bar color for each View Controller:
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
Ad this to your Info.plist and set status bar color from Project -> Targets -> Status Bar Style by desired color.
On the other hand, in your case, you have a navigation controller which is embedded in a view controller. Therefore, you want to different status bar color for each page.
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
Ad this to your Info.plist. Then, create a custom class for your NavigationController. After that you can implement the method:
class LightContentNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
Thats it! Please, inform me whether this was useful!
If Matt's answer isn't working for you, try adding this line of code before you present your viewController.
viewController.modalPresentationCapturesStatusBarAppearance = true
I encountered a bug where setting modalPresentationStyle to overFullScreen does not give the status bar control to the presented view controller or navigation controller.
I was using navigation controller for each tab of UITabBarController. Subclassing UINavigationController and overriding childForStatusBarStyle fixed the issue for me.
class MyNavigationController: UINavigationController {
open override var childForStatusBarStyle: UIViewController? {
return topViewController?.childForStatusBarStyle ?? topViewController
}
}
If you have a modal UIViewController the situation becomes very tricky.
Short answer:
Present modal using UIModalPresentationStyle.fullScreen
override preferredStatusBarStyle (in your modal vc)
call setNeedsStatusBarAppearanceUpdate() in viewWillAppear (in your modal vc)
If you don't want to use UIModalPresentationStyle.fullScreen you have to set modalPresentationCapturesStatusBarAppearance
According to apple doc:
When you present a view controller by calling the
present(_:animated:completion:) method, status bar appearance
control is transferred from the presenting to the presented view
controller only if the presented controller's modalPresentationStyle
value is UIModalPresentationStyle.fullScreen. By setting this property
to true, you specify the presented view controller controls status bar
appearance, even though presented non-fullscreen.
The system ignores this property’s value for a view controller
presented fullscreen.
You can set
vc.modalPresentationCapturesStatusBarAppearance = true
to make the customization works.
Customizing UINavigationController can fix the issue
class ChangeableStatusBarNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return topViewController?.preferredStatusBarStyle ?? .default
}
}
Ref: https://serialcoder.dev/text-tutorials/ios-tutorials/change-status-bar-style-in-navigation-controller-based-apps/

Split View controller occasionally showing detail view on iPhone on iOS 13

We have a universal app with Split view controllers which are embedded within the different tabs. We're observing that on iOS 13 on iPhone, and while switching tabs, the detail view is showed instead of the master view occasionally. We have not been able to single out the pattern and this just happens randomly, but frequently.
I have already referred to UISplitViewController in portrait on iPhone shows detail VC instead of master and
Open UISplitViewController to Master View rather than Detail and we're implementing the delegate for SplitViewController. This delegate also gets called.
class AppSplitViewController: UISplitViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.delegate = self
self.preferredDisplayMode = .allVisible
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
// MARK: - UISplitViewControllerDelegate Methods
extension AppSplitViewController: UISplitViewControllerDelegate {
func splitViewController(_ splitViewController: UISplitViewController, collapseSecondary secondaryViewController: UIViewController, onto primaryViewController: UIViewController) -> Bool {
return true
}
}
I am not sure if other people have observed this on iOS 13 as well or not, but I am not sure why iOS would present detail view occasionally even though we have the proper delegate implementation. Please note that we have not been able to reproduce this on iOS 12. Here is view stack
https://imgur.com/eWO3RQM
Set the UISplitViewController delegate again in awakeFromNib. There appears to be some order of operations issue, from iOS 13 to at least 13.3. I had the exact same issue, and implemented this tip as a result of another answer, which seems to be working.
The below newly introduced method could be helpful if you are using iOS 14 or above
func splitViewController(_ svc: UISplitViewController, topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column) -> UISplitViewController.Column {
return .primary
}

macOS application like Photos.app on macOS

I'm trying to create a macOS app like Photos.app. The NSWindowController has a toolbar with a segmented control. When you tap on the segmented control, it changes out the the NSViewController within the NSWindowController.
What I have so far is an NSWindowController with an NSViewController. I have subclassed NSWindowController where I have the method that gets called whenever the user taps on the segmented control.
Essentially, whatever segment is clicked, it will instantiate the view controller that is needed and set it to the NSWindowController's contentViewController property.
Is this the correct way of doing it?
Also, the NSWindowController, I am thinking, should have properties for each of the NSViewControllers it can switch to that get lazy loaded (loaded when the user taps them and they get held around to be re-used to prevent re-initializing).
Code:
import Cocoa
class MainWindowController: NSWindowController
{
var secondaryViewController:NSViewController?
override func windowDidLoad()
{
super.windowDidLoad()
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
}
#IBAction func segmentedControlDidChange(_ sender: NSSegmentedControl)
{
print("Index: \(sender.selectedSegment)")
if sender.selectedSegment == 3 {
if secondaryViewController == nil {
let viewController = storyboard?.instantiateController(withIdentifier: "SecondaryViewController") as! NSViewController
secondaryViewController = viewController
}
self.window?.contentViewController = self.secondaryViewController
}
}
}
I'm new to macOS development, however, I've been doing iOS for quite some time. If there is a better way, I'd like to know about it. Thanks!!!
to move the tab/segmented-control to the titlebar, you need:
add toolbar to window, and add the controls to the toolbar,
hide title:
class TopLevelWindowController: NSWindowController {
override func windowDidLoad() {
super.windowDidLoad()
if let window = window {
// reminder like style
// window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
// window.styleMask.insert(.fullSizeContentView)
}
}
}
now, toolbar will be merged into the top bar position.

How to switch UIViewController programatically for tvOS with Swift?

Question:
How do I correctly switch between different UIControllerView programmatically for tvOS with Swift?
Details:
For example, by default when I create a tvOS project in Xcode I have FirstViewController and a SecondViewController created. Is it possible to create a ThirdViewController not defined in the tab bar controller that I can switch to programmatically whenever I want?
Yes you can add more vc programmatically in TVOS.
If you have story board with tabbarcontroller change it class to custom tabbarcontroller and inside that you can add more view controllers.
Something like this
class DashBoardTabVC : UITabBarController {
override func viewDidAppear(animated: Bool) {
let vc = UIViewController()
self.viewControllers?.append(vc)
}
}
And you can select any of them like
self.selectedIndex = 0

Color text of status bar in XCode 6-b3 (Swift)

I've try to modify status bar color text but no one answer from this thread doesn't work.
Any especially for XCode 6?
I've tried insert:
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return UIStatusBarStyle.LightContent
}
to UIViewController
also
UIApplication.sharedApplication().setStatusBarStyle(UIStatusBarStyle.LightContent, animated: true)
to AppDelegate.swift
And I've tried change it in info.plist
But it doesn't affect it. How change status bar color text to white?
In your Info.plist you need to define View controller-based status bar appearance to any value.
If you define it YES then you should override preferredStatusBarStyle function in each view controller.
If you define it NO then you can set style in AppDelegate using
UIApplication.sharedApplication().statusBarStyle = .LightContent
Just set " View controller-based status bar appearance == NO " in to your plist and put a single line in your appdelegate class in didfinshLaunching method.
UIApplication.sharedApplication().statusBarStyle = .LightContent
Swift 3.0
Just set View controller-based status bar appearance == NO in to your *.plist and put below code in your appdelegate class in didFinishLaunchingWithOptions method before return.
let statusBar: UIView = UIApplication.shared.value(forKey: "statusBar") as! UIView
if statusBar.responds(to:#selector(setter: UIView.backgroundColor)) {
statusBar.backgroundColor = UIColor.red
}
UIApplication.shared.statusBarStyle = .lightContent
You can change backgroundColor and statusBarStyle as per your requirement.
Be sure to set the View controller-based status bar appearance in your info.plist file to Yes.
Furthermore, if you are in a UINavigationController, you cannot simply set the style in ViewControllers in it. Subclass the UINavigationController and add this to it:
override func preferredStatusBarStyle() -> UIStatusBarStyle {
if let vc = self.viewControllers?.last as? UIViewController {
return vc.preferredStatusBarStyle()
}
return super.preferredStatusBarStyle()
}
Now you can set the bar style in the UIViewController subclass and the UINavigationController will listen to it :).
Keenle is right on, from iOS7 onward, you have to opt out of viewController-based status bar styles before you can set it app-wide.
doc:https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplication_Class/index.html#//apple_ref/occ/instm/UIApplication/setStatusBarStyle:animated:
"To opt out of the view controller-based status bar appearance behavior, you must add the UIViewControllerBasedStatusBarAppearance key with a value of NO to your app’s Info.plist file, but doing so is not recommended."