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
Related
Question
In a UISplitViewController collapsed display, how can I programmatically get back to master view controller?
Detail
I googled it but found no solution. Not sure if I was using the right keyword. This is how I show the detail view controller:
[self showDetailViewController:[[UINavigationController alloc] initWithRootViewController:detail] sender:self];
I also tried these 3 methods respectively, but none of them worked:
if (self.splitViewController.collapsed) {
UIBarButtonItem *backButtonItem = self.navigationItem.leftBarButtonItem;
(1):[backButtonItem.target performSelector:backButtonItem.action];
(2):[[UIApplication sharedApplication] sendAction:backButtonItem.action to:backButtonItem.target from:nil forEvent:nil];
(3):objc_msgSend(backButtonItem.target, backButtonItem.action);
}
navigation items set like thie in detail VC viewDidLoad:
self.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
self.navigationItem.leftItemsSupplementBackButton = YES;
Alright, I have found a solution that seems to work. I have tested it on iPhone 6 and iPhone 6 Plus, but I only just discovered it thirty minutes ago, so It might have some unfortunate side effect which I have not run into yet.
It's in swift. I hope it's clear though. Let me know if you need me to provide it in Objective-C instead.
if let splitViewController = splitViewController {
if splitViewController.collapsed {
let viewControllers = splitViewController.viewControllers
for controller in viewControllers {
// PrimaryNavigationController is the navigation controller I use
// as the split views master view, which is also set as its delegate
// but it could be any UINavigationController that is the
// primary controller of the split view
if controller.isKindOfClass(PrimaryNavigationController) {
controller.popViewControllerAnimated(true)
}
}
}
}
I call this from my detail view when I want to dismiss it.
The code works by checking if the split view controller is collapsed, which is the only state where popping the detail view makes sense (to me anyways). Then it simply looks for the navigation controller currently in play in the split view controller and asks it to pop it's top view controller. This works because when in collapsed mode, the split views master view is the only view controller in the stack. The detail view is collapsed "into" it, and therefore becomes the current top view controller of it, thus is the one that gets popped.
Seems to work. Let me know if it do for you too.
I was looking to do exactly the same, and this code worked for me. I put it in the detail view, hooked up to a button in the navigation bar.
In my application the detail view can segue to itself a number of times and this code gets one back to the master view no matter how deep down the line it gets.
#IBAction func unwindSegueId(sender: AnyObject) {
if (self.splitViewController!.collapsed) {
self.splitViewController!.viewControllers[0].popToRootViewControllerAnimated(true)
}
}
This seems to work (provided you have a navigation controller in your master pane)
if (self.splitViewController.collapsed) {
[(UINavigationController *)self.splitViewController.viewControllers[0]
popToRootViewControllerAnimated:YES];
}
In my app I programmatically change root view controllers based on user actions e.g. login/logout functionality.
In iOS 8 - I'm noticing a strange issue. Even after setting rootViewController on the window, the old hierarchy still persists. I just verified it by capturing view hierarchy.
- (void) logout{
[self.window setRootViewController:[self loadLoginView]];
}
-(UIViewController *) loadLoginView{
WelcomeScreenVC *wsVC;
wsVC = [[WelcomeScreenVC alloc] initWithNibName:#"WelcomeScreenVC" bundle:nil];
UINavigationController *onboardingVC = [[UINavigationController alloc]initWithRootViewController:wsVC];
return onboardingVC;
}
Even after executing this line of code, the old logged in view hierarchy still persists. Would appreciate if anybody can suggest what's happening behind the scenes.
Edit: I just looked at UIWindow setRootViewController documentation and here's what Apple has to say about it:
The root view controller provides the content view of the window.
Assigning a view controller to this property (either programmatically
or using Interface Builder) installs the view controller’s view as the
content view of the window. If the window has an existing view
hierarchy, the old views are removed before the new ones are
installed.
I have noticed the very same thing.
Basically, I have a fairly complicated storyboard that acts as a login/welcome interface. This interface sits in a navigation controller, which presents another navigation controller modally.
After a certain point, the user takes an action that transitions him to the main interface. Using the iOS 8 view debugger I noticed that the old view hierarchy was still around after setting the rootViewController property of the window.
My solution, for now is to use the following code, right before I re-assing the window.rootViewController property:
for (UIView* subView in self.window.rootViewController.view.subviews) {
[subView removeFromSuperview];
}
[self.window.rootViewController.view removeFromSuperview];
It ain't pretty, but it works.
Another odd thing I noticed is that the welcome interface's modally presented viewController is not properly cleaned up using this method. I have to manually dismiss it AND do this clean up.
The best way to fix is:
self.window.subviews.forEach { $0.removeFromSuperview() }
or, in old style:
for view in self.window.subviews {
view.removeFromSuperview()
}
var loginNavigationController: OnBoardViewController?{
willSet{
if newValue == nil {
loginNavigationController?.view.removeFromSuperview()
}
}
}
loginNavigationController = nil
Then set window.rootviewcontroller = {Different VC}
I've been looking this up for a while now, it might have a simple answer:
According to the Apple docs, past ios6, we can subclass UINavigationController. How do we perform a segue from identifier when it prevents anything that isn't a UINavigationController. Mainly:
uncaught exception 'NSGenericException', reason: 'Could not find a
navigation controllerfor segue 'profileSegue'. Push segues can only
be used when the source controller is managed by an instance of
UINavigationController.
I'm using JaSidePanels and my center panel (navigation) needed to be subclasses for a delegate as there is a menu on the left panel that I want to switch the views when clicked.
#interface CenterViewController : UINavigationController <MenuDelegate>
Basically, since this object is a CenterViewController at runtime, is there a way to cast it to its superclass? I've tried [self superclass] but that didn't work (same error).
I need to call this code in the CenterViewController. Is it possible to move it to UINavigationController?
- (void)viewDidLoad
{
RootViewController *rootViewController = (RootViewController *)[[[[UIApplication sharedApplication] delegate] window] rootViewController];
MenuViewController *leftViewController = (MenuViewController *)rootViewController.leftPanel;
// Store a reference to the center view controller in the left view controller's delegate property
leftViewController.menuDelegate = self;
[super viewDidLoad];
}
- (void) MenuItemSelected: (NSString*) item
{
if ([item isEqualToString:#"Home"]) {
//These would throw the error since we're not technically a "UINavigationController"
//[self performSegueWithIdentifier: #"mapViewController" sender: nil];
} else if ([item isEqualToString:#"Profile"]) {
//[self performSegueWithIdentifier: #"profileSegue" sender: self];
}
}
EDIT:
More information with pictures.
I'm curious as to how navigation controllers should work with side panels. I've looked at many other plugins for sidepanels and here is an example. Even though it works, why does it have 2 nav controllers?
Here is our current setup:
So basically, am I thinking about this wrong in the sense that I want to push a new VC to the existing NavVC? Would it be better to push a new NavVC when a menu button is pressed? What would happen when we go into a subview from the Maps view. Would the menu be accessible via sliding?
If you look carefully at the message you will see that your problem isn't caused by subclassing UINavigationController it is because you are executing the segue against your (subclassed) UINavigationController. Push segues are executed against a UIViewController that is embedded in or managed by a UINavigationController, with the system then finding the managing UINavigationController via the view controller's navigationController property in order to execute the push.
The message you have received says
...Push segues can only
be used when the source controller is managed by an instance of
UINavigationController
In your case the source controller is an instance of UINavigationController, it isn't managed by a UINavigationController.
You haven't said exactly how your app navigation works, but I have a suspicion that a UINavigationController isn't the right tool to use anyway. If you are using the side menus to allow the user to select the central content in a random way (i.e. the user could select the first option then the fifth and then go back to the first) then you should probably just have a central view into which you present the selected view. Pushing views onto a UINavigationController will end up with a large 'stack' of views unless you pop the current view controller before pushing the new one, which is even more complicated and not the visual effect you are looking for.
You can still achieve the 'push' style transition but you will need to use a custom segue. If it were me I would probably push from the left if the user selected a menu item that was closer to the top than the current option and from the right if the new item was closer to the bottom than the current, but again I am making assumptions on how your app navigation works.
UPDATE
Yes, I think you are on the right track with the section of your updated question. Navigation controllers are for navigating a series of related views in a hierarchical manner - think of the Settings app - you select "general" or "wall paper" or whatever - each of these then has a series of views that you can navigate through; up and down a stack.
In your app it looks like home, profile and settings should each be navigation controllers. Your root view would then just be a view. Your menu would select which view controller to present in the root view - this is like a tab bar controller, except your menu takes the place of a tab bar.
You can allocate your home, profile & settings view controllers in your appDelegate and store them to properties of the appDelegate. Then you can use something like this:
- (void) MenuItemSelected: (NSString*) item
{
myappDelegate *app=(myappDelegate *)[UIApplication sharedApplication].delegate;
[delegate.currentViewController removeFromParentViewController];
UIViewController *newController;
if ([item isEqualToString:#"Home"]) {
newController=app.homeViewController;
} else if ([item isEqualToString:#"Profile"]) {
newController=app.profileViewController;
}
if (app.currentViewController != newController)
{
[app.currentViewController removeFromParentViewController];
[app.rootViewController addChildViewController:newController];
app.currentViewController = newController;
}
I am using view controller containment, and as part of my implementation a child view controller needs to disable a bar button item of the navigation controller of the parent view controller. The theory is that being a child view controller, I had access to the same navigation controller (in my context anyway) as the parent view controller's.
From debugging I can see that self.navigationController and self.parentViewController.navigationController were set to the same address.
For example:
NSLog(#"%# - %#", self.navigationController,
self.parentViewController.navigationController);
NSLog(#"%# - %#", self.navigationItem,
self.parentViewController.navigationItem);
NSLog(#"%# - %#", self.navigationController.navigationItem,
self.parentViewController.navigationController.navigationItem);
Resulted in the following console log:
<UINavigationController: 0xc482290> - <UINavigationController: 0xc482290>
<UINavigationItem: 0xa5f3620> - <UINavigationItem: 0xc482490>
<UINavigationItem: 0xa5f36e0> - <UINavigationItem: 0xa5f36e0>
Results
Navigation controllers are the same, as expected (self->navController == self->parent->navController).
Navigation item's are different, this is expected. Each view controller has it's own nav item (self->navItem != self->parent->navItem).
Accessing the navigation controllers nav item from the parent or the child view controller is identical, as expected (self->navController->navItem == self->parent->navController->navItem)
So I now ask: why didn't disabling a bar button item in the child view controller with the following code work:
self.navigationController.navigationItem.rightBarButtonItem.enabled = NO;
This should be synonym to the following (which does work as expected) given their shared address:
self.parentViewController.navigationController.navigationItem.rightBarButtonItem.enabled = NO;
Update
I was reading my logs too fast. Indeed the parentViewController and the navigation controller have 2 different navigation items. I missed this in my example here because the memory addresses were extremely close: 0xa5f3620 != 0xa5f36e0
A UINavigationController has it's very own navigationItem (like any other UIViewController) that will only come into play if you push it onto the stack of another UINavigationController.
Generally you aren't going to push one UINavigationController onto another so manipulating self.navigationController.navigationItem is pointless.
EDIT:
To check ... I've just aded:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UINavigationItem *myNavControllersItem = self.navigationController.navigationItem;
UINavigationItem *myItem = self.navigationItem;
NSLog(#"%# - %#", myNavControllersItem, myItem);
}
to a VC that I'm pushing onto a NavController and get:
<UINavigationItem: 0x1ed0c170> - <UINavigationItem: 0x1ed46330>
Additionally, if I add:
self.navigationItem.rightBarButtonItem.enabled = NO; to viewDidAppear the right button gets disabled.
if I add:
self.navigationController.navigationItem.rightBarButtonItem.enabled = NO; it does not.
Well basically the problem is the way the navigation controller updates the bar button items, basically the buttons are update whenever the top-level child is changed.
For the right bar button item, if the new top-level child has it's own bar buttons items then the navigation controller will select the existing one, if not nothing will be displayed.
So in your case the parrentViewController already has a navigationItem.rightBarButonItem which is not disabled, by calling self.navigationController.navigationItem.rightBarButtonItem.enabled = NO; you are disabling the child's nabigationItem.rightBarButtonItem and you can't see this because the updates are made only when the top-level child is changed which is not your case.
So in order to disable the parentViewController right button you should do:
self.parentViewController.navigationItem.rightBarButtonItem.enabled = NO
For more details regarding the nav bar button updates please check apple docs regarding the Updating Navigation Bar Items
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.