In my project(written programmatically) in some files I hide the Navigation Bar, in other files I change the Navigation Bar color. Yes, I can do this just writing code at every single file where I want some specific Navigation Bar, but its so buggy and not the right way.
I tried to write a class, subclass of UINavigationController, where I loop through all my viewControllers and hide the Navigation Bar in specific files, but its not working.
Can anyone please help me to figure out it? What is the right way to control all the Navigation Bars changes(actions) from one file?
Thank you in advance.
What you want to do is subclass the UINavigationViewController and override the pushViewController method. Your ViewControllers should conform to a protocol and you should check inside the pushViewController if the ViewController conforms to that protocol and if so check if it implemented the methods from the protocol and based on that layout your UI.
Example:
-(void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
if ([controller conformsToProtocol:#protocol(MyProtocol)]) {
id<MyProtocol> protocolVC = (id)controller;
if([protocolVC barColor]){
UIColor *color = [protocolVC barColor];
//Set the bar color
}
}
[super pushViewController:viewController animated:animated];
}
Related
Here is the problem I am having. I am unable to set the UINavigationBar title for the views I have contained within a UIPageViewController.
The basic architecture of the app is as follows.
The root view controller for the app is a UITabBarController, with 5 navigation controllers contained in it.
The first Navigation controller, which is the one I am having issues with, contains a page view controller and this page view controller contains a number of UIViewControllers.
I want that, when I swipe through each of these view controllers, I can set the title in the UINavigationBar.
I have tried the following:
In the UIViewController contained within the page view controller, I have tried [self setTitle:#"Title I want"] - it didn't work.
Within the same UIViewController I have also tried [self.navigationBar.navigationItem setTitle:#"Title I want"] - this also didn't work.
I also tried setting the title for the View controller and attempted to extract that inside the PageViewControllers delegate method transitionCompleted, but this didn't work either.
I am wondering should I go back to the drawing board, and whether I am going down a rabbit hole with this view layout architecture. Has anyone else encountered this issue and if so, how did you solve it?
Edit: I must also add that I am doing this programatically.
Thanks for the help.
So, in the end I came up with a way to get this working, albeit not the cleanest solution that I wanted, but suitable for the purpose nonetheless.
I created a new class called PageLeafViewController and set up its init method as below. Child view controllers of a page view controller inherit from this. Here is the code.
Code sample
- (id)initWithIndex:(NSUInteger)index andTitle:(NSString *)navBarTitle; {
if(self = [super init]) {
self.index = index;
self.navBarTitle = navBarTitle;
}
return self;
}
These can be initialised like so before being added to the UIPageViewController.
Code sample
ChildViewController *aChildViewController = [[ChildViewController alloc] initWithIndex:1 andTitle:#"A Title"];
You will need to add a UIPageViewControllerDelegate to your interface for your page view controller. This is so you can implement the code for the delegate methods for when your view transition has been completed, and you need to set the title.
When the UIPageViewController loads, I grab the first view controller and get its title, setting it to the UINavigationController navigation bar
Code sample
PageLeafViewController *initialViewController = (PageLeafViewController *)[self viewControllerAtIndex:0];
[self.navigationItem setTitle:initialViewController.navBarTitle];
When a transition occurs, we set the title again to that of the new child view controller, when the transitioning into view has completed.
Code sample
- (void)pageViewController:(UIPageViewController *)pageViewController didFinishAnimating:(BOOL)finished previousViewControllers:(NSArray *)previousViewControllers transitionCompleted:(BOOL)completed {
PageLeafViewController *currentLeaf = (PageLeafViewController *)[self.pageViewController.viewControllers lastObject];
[self.navigationItem setTitle:currentLeaf.navBarTitle];
}
Note: The above gets called automatically when a new child view controller has been displayed.
While this is not the most elegant solution it works for now, and I don't think its possible to call a function from within a child view to update the NavigationBar title, unless someone wants to correct me?
Hope this helps.
I don't think you're supposed to set the title on the navigationBar, have you tried self.navigationController.title = #"Title"; ?
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 there a method to know when a UIView was pushed by the user from the More view of a UITabbar ?
I have multiple views in a UITabBar and some end up in the More view where they are listed. Il'd like to be able to know when the view is pushed from this More view as opposed to when it is pushed from a UITabBarItem
Thanks !
You can just check if the controller's navigation controller is the more navigation controller:
-(void)viewDidLoad {
[super viewDidLoad];
if (self.navigationController == self.tabBarController.moreNavigationController) {
NSLog(#"Launcehed from more");
}else{
NSLog(#"Launcehed from tab bar");
}
}
Your question says UITabBar but I suspect you mean UITabBarController. Based on how some options "end up in the More view", which is provided automatically by UITabBarController.
If you do mean UITabBarController then yes a UIViewController presented by a UITabBarController can very easily determine whether it is in the more options or not.
[self.tabBarController.moreNavigationController.viewControllers containsObject:self];
This will return a BOOL that is true if the option for that view controller (self) is listed in the More section of the UITabBar.
You could subclass the UIView and add a property sentFrom which you would pass along to the UIView when adding it in whatever controller. There is not an already implemented method that does it, so you have to keep track of such things by yourself.
No, UITabbarController doesn't expose that information.
I got an app with my custom TabBar Controller Class.
I tried to implement tabbar controller delegate method:
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(#"%i",tabBarController.selectedIndex);
}
But it doesnt work. Why?
in ViewDidLoad i write:
self.tabBarController.delegate = self;
And in .h i implement:
#interface BaseViewController : UITabBarController <UITabBarControllerDelegate>
In your custom TabBarController, do not use
self.tabBarController.delegate = self;
But use
self.delegate = self;
.tabBarController returns the nearest ancestor in the view controller hierarchy that is a tab bar controller, but your custom TabBarController IS the controller you want to target, so no need to search in its hierarchy
You have said, that it's your custom TabBarController. What is the customisation you've done? If you changed the TabBar panel and replaced it with your own to use
setSelectedIndex:
setSelectedViewController:
methods manually, then you should call delegate's methods manually too.
According to the Apple's documentation:
There are two types of user-initiated changes that can occur on a tab
bar:
The user can select a tab.
The user can rearrange the tabs.
Both types
of changes are reported to the tab bar controller’s delegate, which is
an object that conforms to the UITabBarControllerDelegate protocol.
Also check the UITabBarControllerDelegate Protocol Reference
In iOS v3.0 and later, the tab bar controller calls this method regardless of whether the > selected view controller changed. In addition, it is called only in response to user taps in > the tab bar and is not called when your code changes the tab bar contents programmatically.
Delegate will respond only if user interacts with its UITabBar control.
I'm creating a library which will add a view at the bottom of the application (when my library is integrated in application).
I'm using view controller's view's frame parameter to get the size of the view and calculation my library's view frame according and showing it.
The problem is that when navigation bar is there, my view is going still below the actual view visible. So, i want to know whether current view controller is based on navigation controller or not and whether navigation bar is visible in that view or not. how can I find that?
I'm late with the reply, but for other persons who try to do the same thing (like me :D).
This code may solve your problem:
id nav = [UIApplication sharedApplication].keyWindow.rootViewController;
if ([nav isKindOfClass:[UINavigationController class]]) {
UINavigationController *navc = (UINavigationController *) nav;
if(navc.navigationBarHidden) {
NSLog(#"NOOOO NAV BAR");
} else {
NSLog(#"WE HAVE NAV BAR");
}
}
UINavigationBar inherits from and has all the fine properties and behaviors of UIView and one of these properties is hidden.
So for your view, if you can get a handle to your navigation bar, all you need to do is check to see if hidden is YES or NO.
one way to do this would be to have a UINavigationController property or accessor (setter & getter) for your library so whoever makes use of the library can set the navigation controller and/or bar on your library's behalf.
Up to date check from a view controller context:
let navHidden = navigationController?.isNavigationBarHidden ?? true
if needsCloseButton || navHidden
{
// here add an alternative ways to get out since back button is not here, say add a close button somewhere