UISearchController changing status bar color on invocation - ios

I have the following code in my app, specifically in viewDidLoad: that sets up my UISearchController.
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.hidesNavigationBarDuringPresentation = NO;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.definesPresentationContext = NO;
self.searchController.searchBar.scopeButtonTitles = #[];
self.searchController.searchBar.searchBarStyle = UISearchBarStyleProminent;
[_tableView setTableHeaderView:_searchController.searchBar];
Whenever the search bar (which is added to the tableView) is invoked, the UIStatusBar color changes from UIStatusBarStyleLightContent to dark (white to black). Now, I figured out if I set,
self.definesPresentationContext = NO;
to the following:
self.definesPresentationContext = YES;
the issue is solved and the UIStatusBar color is preserved. However, another issue arises. When self.definesPresentationContext is set to YES
, upon invocation the search bar shifts down for some reason, coincidently (or rightfully so) right under where the bottom of the UIRefreshControl displays on the tableView.

Setting View-controller based status bar appearance to No is not a solution if you want the view controllers to define how the status bar looks.
My solution consisted of two things:
Make sure the presenting view controller has definesPresentationContext set to YES
Make sure both the view controller that is pushed and the pushing view controller are laid out beneath the navigation bar (set extendedLayoutIncludesOpaqueBars to YES)

As of iOS 10 (maybe earlier?), if you have "View controller-based status bar appearance" set to YES in your Info.plist, simply set the preferredStatusBarStyle in the UIViewController that the UISearchController is included in.
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
(you don't need to subclass or create a category/extension of UISearchController to override preferredStatusBarStyle... it uses the preferredStatusBarStyle that you set in your UIViewController)

I needed full control over my status bar colour. I use the extensions found here to ensure that the visible view controller is setting the preferred status bar colour.
For me it was therefore necessary to override UISearchController and override preferredStatusBarStyle and return the style I wanted.

If you ViewController is inside a TabBarController then -
Instead of
self.definesPresentationContext = YES;
Use self.tabBarController.definesPresentationContext = YES;
This worked for me in above scenario.

The status bar that is displayed when the search controller is presented (is active) belongs to the search controller. To set the preferred status bar style you must add a category to UISearchController and then override the preferredStatusBarStyle method.
Below is an example of the implementation file of the category:
#implementation UISearchController (Customization)
-(UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
#end

Or we can write an extension on Swift (version 2, but you can translate it to 3 easily):
extension UISearchController {
override public func preferredStatusBarStyle() -> UIStatusBarStyle{
if Theme.lightTheme() {
return UIStatusBarStyle.Default
}
else {
return UIStatusBarStyle.LightContent
}
}
}
Where Theme is a class that regulate app colour theme.

Related

Setting statusBar style does not work Objective-C

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];

Presented View Controller Status Bar Color is Different, How to Change It To Default?

I have a presented view controller. However its status bar color is different then its navigation bar color. How do I make sure they are same color?
Try below code, if it help:
Set same style as current viewController to viewController being presented as follows:
//newViewController is your ViewController
newViewController.navigationBar.barStyle = self.navigationController.navigationBar.barStyle;
[self presentViewController:newViewController animated:YES completion:nil];
Also, 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.
- (UIStatusBarStyle)preferredStatusBarStyle
{
return self.topViewController.preferredStatusBarStyle;
}
OR
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
Make sure this function is in your presenting view controller and the view controller being presented.
iOS 13 or later we faced with non full screen presented Controllers. In this case, you can use this documentation.
//change the background of status bar
UIView *addStatusBar = [[UIView alloc] init];
addStatusBar.frame = CGRectMake(0, 0, self.view.frame.size.width, 20);
addStatusBar.backgroundColor = bgStatus;
[self.view addSubview:addStatusBar];
//change the text color of status bar from black to white
- (UIStatusBarStyle)preferredStatusBarStyle {
return UIStatusBarStyleLightContent;
}

Can't edit UISearchController UISearchBar in navigationItem titleView

When setting a UISearchController search bar in the navigationItem titleView, the search bar can't be edited.
In my viewDidLoad I am configuring a UISearchController.
self.searchViewController = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SearchViewController class])];
self.searchViewController.delegate = self;
self.searchController = [[UISearchController alloc] initWithSearchResultsController:self.searchViewController];
self.searchController.delegate = self;
self.searchController.searchResultsUpdater = self;
self.searchController.searchBar.delegate = self;
self.navigationItem.titleView = self.searchController.searchBar;
I can't tap the search bar. The cursor does not appear and there is no user interaction.
Oddly, if I initialize the UISearchController locally without setting it to a property, then I can edit the search bar, just no delegate callbacks.
self.searchViewController = [self.storyboard instantiateViewControllerWithIdentifier:NSStringFromClass([SearchViewController class])];
self.searchViewController.delegate = self;
UISearchController *searchController = [[UISearchController alloc] initWithSearchResultsController:self.searchViewController];
searchController.delegate = self;
searchController.searchResultsUpdater = self;
searchController.searchBar.delegate = self;
self.navigationItem.titleView = searchController.searchBar;
Another interesting behavior is that the clear button works (if some text is set in the search bar while initializing).
I had the same issue.
Imagine you have FirstViewController and SecondViewController, and booth have a UISearchBar on the titleView.
To fix the problem I had this code to booth UIViewController's.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.definesPresentationContext = true
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.definesPresentationContext = false
}
I am setting self.definesPresentationContext = YES; in the view controller that presents the view controller in question.
This must be set to self.definesPresentationContext = NO; in viewWillAppear:.
Now the search bar in the presented view controller can be edited.
Set your search bar to navigation title view :
self.navigationItem.titleView = self.searchBarTop;
then just set this view either left/right button of Navigation Bar
UIBarButtonItem *searchBarItem = [[UIBarButtonItem alloc] initWithCustomView:searchBar];
self.navigationItem.rightBarButtonItem = searchBarItem;
I hope this will work for You!!
I have just solved a very similar problem in my app, and thought I'd share the solution in case the existing solutions don't fix it for you.
In my case I had a tab bar application, with my custom controllers in the tabs, embedded in the navigation controllers. The symptoms were exactly the same, the search bar showed in the title area of the navigation bar, but was not interactive.
I have discovered that the problem was that I used my custom subclass of the UITabBarController and I have overriden the viewWillAppear(animated:) method, but forgot to call super.viewWillAppear(animated:) in the implementation. The additional symptom was that when I switched the tabs, the search bar suddenly became interactive and everything worked fine, just the interaction on the initial tab was disabled.
I hope this helps someone.
In your first block of code, you're instantiating with a SearchViewController identifier. In the second, you're using HBSearchViewController. This suggests that there might be another difference in your code besides using / not using an outlet.

Search Results Visibly Scrolling Underneath the Status Bar

When performing a search on a dataset via a UISearchBar, the search results successfully display in the UITableViewController's UITableView. However, when scrolling down through the results, the UITableView's rows visibly appear underneath the UINavigationBar and the simulator's status bar.
This obviously is not the look that I'm going for. Ideally, I would like the UISearchBar to act as the UITableView's header with all search results being contained below the UISearchBar's scope buttons, but my attempts have been unsuccessful.
Below is the Storyboard setup of the relevant UITableViewController and its UITableView's properties.
Below is the relevant code that I am using to setup the UISearchController and its UISearchBar.
BallotTunesSearchTableViewController.h
#interface BallotTunesSearchTableViewController : UITableViewController <UISearchControllerDelegate, UISearchResultsUpdating, UISearchBarDelegate>
BallotTunesSearchTableViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.appDelegate = [[UIApplication sharedApplication] delegate];
// Initialize the search controller
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
// Setup the search bar
self.searchController.searchBar.delegate = self;
self.searchController.searchBar.scopeButtonTitles = [NSMutableArray arrayWithObjects:SongScopeName, ArtistScopeName, AlbumScopeName, nil];
self.tableView.tableHeaderView = self.searchController.searchBar;
}
Update: Note that the UITableViewController is embedded in a UINavigationController, and when setting the translucence of the UINavigationBar to NO, the UISearchBar slides off the view along with the UINavigationBar.
Also note that I am not implementing the UISearchBar in Storyboard (however, I may take that route if I can't get my current setup to work).
After several face palms, it all came down to the lack of this line of code:
self.definesPresentationContext = YES;
Setting the presentation context to YES indicates that the view controller's view should be covered when the view controller presents the UISearchController.

The correct way to set a light status bar text color in iOS 7 based on different ViewControllers

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];
}

Resources