How would I implement nested Tab Bar Controllers in an IOS project - ios

I"m trying to implement something like in the wireframe I basically want a Tab Nav Controller on the bottom, and then within on of the views, have another tab like controller. I'm just a little confused as to where I would start with this.

I very much doubt there's a standard user control for that top tab-like control that you want. You may have to construct your own controller and view for that, then manually manage the center view when you get taps on your "View 1" and "View 2" labels.

you should use not Tab Bar Controller, but Navigation Controller toolbar property. It looks like tab bar items but there's no need in implementation another controller instance.
Look at the UINavigationController reference page
A navigation controller object manages an optional toolbar in its view
hierarchy. When displayed, this toolbar obtains its current set of
items from the toolbarItems property of the active view controller.
When the active view controller changes, the navigation controller
updates the toolbar items to match the new view controller, animating
the new items into position when appropriate.
This is some UIViewController class:
- (void)createToolbarItems
{
UIImage *background = [[UIImage imageNamed:#"navbar"] resizableImageWithCapInsets:UIEdgeInsetsMake(2, 2, 2, 2)];
[self.navigationController.toolbar setBackgroundImage:background forToolbarPosition:UIBarPositionBottom barMetrics:UIBarMetricsDefault];
self.navigationController.toolbar.delegate = self; // optional
//... create some UIBarButtonItem items
self.toolbarItems = #[item1, space, item2, space, item3, space, item4]; // we set items not to navigation controller instance, but the current controller
}
- (void)showToolbarAnimated:(BOOL)animated
{
if(!self.toolbar)
return;
[self.navigationController setToolbarHidden:YES animated:animated];
}

Related

Popping UIViewController causes previous UIViewControllers View to change position

I have a UINavigationController with a UIViewController set as it's rootController, it contains a background on its UIView using an image set just under the navBar. I then push onto the navigation controller a new UIViewController and when the back button is pushed, the previous controller looks different. Using the visual debugger I can see that the self.view has moved entirely down below the navBar where previously it was at the top. I have no idea and been racking my brains as to why this might be happening
-(void)pushIPhoneMessagingContactsController:(MessageContactsViewController *)contactsController{
self.selectorView.hidden = YES;
[self.navigationController pushViewController:contactsController animated:YES];
}
On the RootViewController (iPhoneMessagingNotificationsController)
-(void)viewWillAppear:(BOOL)animated{
self.selectorView.hidden = NO;
[[[self navigationItem] leftBarButtonItem] setTintColor:[UIColor blackColor]];
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];
if ([_displayType intValue] == MESSAGES_SHOWING) {
[self.notificationsViewController.view removeFromSuperview];
[self.contentView addSubview:_messagesViewController.view];
} else {
[self.messagesViewController.view removeFromSuperview];
[self.contentView addSubview:_notificationsViewController.view];
}
}
It seems the offending line was in the viewWillAppear method of the pushed UIViewController
self.navigationController.navigationBar.translucent = YES;
Somewhere else this navigationBar gets set as translucent:
[self.navigationController.navigationBar setBackgroundImage:[UIImage new]
forBarMetrics:UIBarMetricsDefault];
self.navigationController.navigationBar.shadowImage = [UIImage new];
self.navigationController.navigationBar.translucent = YES;
and to make it solid colour again:
self.navigationController.navigationBar.shadowImage = nil;
self.navigationController.navigationBar.translucent = NO;
but this code seems to mess with the layout so perhaps there is another way to change the opacity of the navBar and statusBar without affecting the layout?
What you're currently trying to do is hide or show a selectorView which really only should appear for one specific view controller.
Here's an encapsulated way to solve this that makes your selectorView a part of the root view controller, removing the connection from other view controllers. They no longer have to know about it or hide it.
Add your selectorView to your rootViewController's navigation bar titleView. (You can do this in code, or drop it in Storyboard and add an IBOutlet for it.)
self.navigationItem.titleView = selectorView;
Now when you push another view controller, its title will replace your rootViewController's selectorView title (view). Your other view controllers don't need to know anything about that view.
This is a good design approach in general. Anytime you have a control that should only appear on one view controller's navigation bar, you want to make it a part of that view controller's navigationItem (titleView, or left/right bar button items.) iOS will display the control when it presents that view controller, and hide the control when that view controller is no longer the top view controller in the navigation controller stack.
As for the 64-pixel height issue, it's likely related to some complexity in the rootViewController hierarchy that shouldn't be there.
In iOS 7/8, a view's content, by default, appears under a translucent navigation bar. Apple freely managed this for you, by insetting the first view of the view hierarchy.
From your code, it appears that you're trying to "hide" or "show" the (un)selected viewController's view.
Each view controller should have a view it controls. A view controller shouldn't be trying to control other view controller's views, or adding other view controller's views to its own view hierarchy.
Here's Apple's recommended way to approach this. Use a containerView in your rootViewController. The whole purpose of a container view is to encapsulate a view controller within a view. As your selectorView changes which view to show, you have your container view transition from one view controller to the other. (If you're not familiar with how to do that, check out this answer.)
Pin the containerView to the rootViewController's view, so Auto Layout can size it for you.
Your view hierarchy now looks like view -> containerView, instead of view -> hidden view of unselected view controller, shown view of selected view controller. Apple can adjust the first view's inset, and nothing gets incorrectly offset (by the height of the navigation control).
Update:
This question talks about scrollViewInsets and how they can be set on a view-controller-by-view-controller basis. If you do have a view controller, and you don't want its content to appear under a bar, uncheck that box.
But the best way to handle this is to "standardize" your UI, so it isn't varying from view to view. Either make the bar always be translucent, or not always be translucent. This makes transitions less "jarring" for the users.

How to add Navigation bar to view controller with tab bar item

I have an app that uses bottom tabs aswell as a side menu, to have the button that initiates the side menu i use the typical three line menu button, to put that there I have a Navigation Bar. With the bar in place there is no way I can get the bar to be on top of the screen. I built it with interface builder, and heres a screenshot. The question is how do i have the navigation bar alone without the other grey bar above it?
The issue you're encountering is due to the fact that you're manually creating a navigation bar for your view controller, instead of using the bar that you get for free by embedding the view controller in a tab bar controller, hence the reason you see two bars. The other answer suggesting hiding the auto-generated navigation bar is not the correct solution. Instead, you should place your menu button and view title in the auto-generated bar instead of manually creating your own (you almost never want to do that, in-fact).
So what you should do instead is set the title property of your view controller to be "News", and the leftBarButtonItem property of the view controller to be your hamburger menu button (an instance of UIBarButtonItem initialized with an image for the icon).
For example (inside your view controller's viewDidLoad method or wherever appropriate):
self.title = #"News";
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"menuIcon"] style:UIBarButtonItemStylePlain target:self action:#selector(showSideMenu)];
If you want to remove the topmost navigation bar you need use self.navigationController.navigationBarHidden = YES; for view controllers that used for tabs in UITabBarController:
// StoriesViewController.m
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.navigationController.navigationBarHidden = YES;
}

alternating between toolbar / tab bar

my app is structured as follow: UITabBarController > UINavigationController > ViewControllerOne > ViewControllerTwo.
the UINavigationBar has at the bottom the tab bar, now when the user navigates into the second view controller, i want to be able to hide the tab bar and replace is with a tool bar. i tried this code:
[self.navigationController.tabBarController.tabBar setHidden:YES];
[self.navigationController.toolbar setHidden:NO];
when i run the app the tab bar is hidden but the toolbar doesn't appear. plus, since the last VC is a table view controller, when i scroll through the cells there is a white gap between the table and the bottom of the view. how can i fix that?
That won't work because when you hide the tab bar like that the subviews won't be adjusted properly (that's why you get the white space). You'll have to use
self.hidesBottomBarWhenPushed = YES;
In your init method or awakeFromNib... and then
[self.navigationController setToolbarHidden:NO animated:YES];
In the viewDidLoad for example.
That way the tab bar controller's view is going to layout correctly it's subviews when you hide the tab bar. Just remember to call self.hidesBottomBarWhenPushed = NO; in your first view controller otherwise the tab bar is still going to be hidden when the second view controller is popped from the navigation stack.
Try to assigning toolbar with appropriate frame and adding it to self.tabBarController.view

iOS Right navigation button does not display in UINavigationBar

I have following view controllers hierarchy:
View Controller VC1
Tab Bar Controller TBC1 - configured in storyboard to lead to a Table View Controller TVC1 and a Map View Controller MVC1
Table View Controller TVC1
Table View Controller TVC2
In VC1, I do this:
[self.navigationController pushViewController:TBC1 animated:YES];
This rightly brings up tab bar controller, with TVC1 in focus.
TVC1 shows back button in its navigation bar (programmatically created from VC1 code), which will get me to VC1, which is expected.
However, from TVC1 onwards, I need one more navigation to TVC2. I am trying to add right button to the TVC1 navigation bar for this, but it doesn't show up.
Here is the code I use in TVC1 (rightButton is UIButton type property of TVC1):
self.rightButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemAdd
target: self
action: #selector(MySelector:)];
self.rightButton.style = UIBarButtonItemStyleBordered;
self.rightButton.title = #"";
self.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects: self.rightButton, nil];
(specified blank title and style just to ensure if that's the issue which is causing this, I don't actually need those values)
MySelector is declared in TVC1.h as:
- (void) MySelector:(id)sender;
And it is properly implemented, too.
But rightButton above does not display in TVC1 navigation bar.
What am I missing?
I suspect its with TBC1 (tab bar) that comes between VC1 and TVC1, and somehow it resets navigation properties.
But then I argue that I see navigation bar on TVC1, and a left button leading to VC1.
I checked that in TBC1, self.navigationItem.rightBarButtonItems has 1 object inside which is definitely the rightButton I am adding.
Where am I wrong?
Note: Above is found in all of iOS 5.0, 5.1 and 6.0 simulators.
It seems to me that your are missing UINavigationController between TVC1 and TVC2 in your storyboard. If you are using storyboards then you can create navigation item Add button type on the navigation controller itself and have a PUSH segue to TVC2. See this diagram if that makes sense. If this doesn't solve your problem then please upload example code and I will have a look.
[EDIT]
I had reproduced your issue by creating your view controllers structure in storyboard.
If you notice here TVC1 doesn't have it's UINavigationController but it is inheriting it from VC1. Solution to your problem is rather than adding rightButton onto self add it to self.parentViewController and you will see rightButton in TVC1. But mind you it will also appear in MVC1 as it is belong to TBC1's parent. You can hide right bar button in MVC1's viewWillAppear if you don't want it there. Following is the code.
self.rightButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem: UIBarButtonSystemItemAdd
target: self
action: #selector(MySelector:)];
self.rightButton.style = UIBarButtonItemStyleBordered;
self.parentViewController.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects: self.rightButton,nil];
If you want to Add right button into the TVC1's navigation controller then you need Embed TVC1 into UINavigationController. To do this, select TVC1 screen in the storyboard -> Editor -> Embed In->Navigation Controller. When you do this your code will also work and will show you right button but you will have two navigation controllers(see image below) in it because of your structure of storyboard. You will need to hide Parent's navigation controller in to the TVC1's view did load and have left button to Pop to Parentview Controller. You do the same in MVC1.
Hope this helps! Happy coding :)
Had the same problem recently in Swift and found that embedding the child view in a navigationController was still the correct way to be able to access the rightBarButtonItems.

creating button for popover view anchor at run time

This may not be possible, but I'm hoping someone will have an idea how to do it.
I have an app I'm porting from iPhone only to Universal. On the iPhone, I'm using a Tabbed application. I use three tabs for the normal data to be displayed. I have a forth tab that's only displayed if certain conditions are met. To add the tab, I do:
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone)
{
UITabBarController *tabController = (UITabBarController *) self.rootViewController;
NSMutableArray* newArray = [NSMutableArray arrayWithArray: tabController.viewControllers];
[newArray addObject: [theStoryboard instantiateViewControllerWithIdentifier: #"AdditionalView-Phone"]];
[tabController setViewControllers:newArray animated:YES];
}
For the iPad, I have enough space on the initial view to display everything from the main three tabs in the iPhone UI. So all I need is one additional (small) view for the "Additional" data. I wanted to do it using a popOver view, so I set up the initial view with a Nav bar and popover button as in the Utility App template. But now I'm stuck. I can't figure out how to create that popover button at run time and make it do the segue to the popOver view properly. I can add the button like this:
UIBarButtonItem *flipButton = [[UIBarButtonItem alloc] initWithTitle: #"Modem" style: UIBarButtonItemStylePlain target: self action: #selector(togglePopover:)];
self.navBar.topItem.rightBarButtonItem = flipButton;
but I get an exception: 'NSInternalInconsistencyException', reason: 'UIStoryboardPopoverSegue must be presented from a bar button item or a view.' I'm pretty sure this is because I don't have an anchor set for the popOver segue. The button doesn't exist in the storyboard, so I can't set it there. And I can't seem to find an API to set it at run time.
I also tried creating the button in IB, but not in the view hierarchy, and then just setting the rightBarButtonItem property to my existing button. That also works, but I still can't set that button as the anchor for the popover view. I can set the Navigation Bar as the anchor, but that makes it anchor to the title in the nav bar, which looks silly.
Any ideas?
I had the same problem and solved it by creating a UIBarButtonItem in the Storyboard for the view controller but not part of the view hierarchy.
In IB, Drag a bar button item to the dark bar below the view controller view, drop it next to the "First Responder" and "View Controller" icons. Create a (strong) IBOutlet for it. Then create a popover segue from it to the destination view controller by dragging from the bar button item to the destination. It seems like this is the only way to set it as the anchor. Choosing it as the anchor for an existing segue does not work (looks like an IB bug).
In viewDidLoad you can assign this bar button item to the navigationItem (or where ever you like) and the segue works as expected.
I was curious about this too so I made a quick test project. You're right, there doesn't seem to be a way to configure the popover segue at runtime or add an anchor point to a button that's not in the view hierarchy using Interface Builder.
My solution was to set everything up in IB with the UIBarButtonItem visible and connected to an IBOutlet property, then remove it from the navigation bar in -viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = nil;
}
Then I simply add it back or remove it by tapping another button:
- (IBAction)toggleBarButtonItem:(id)sender
{
UIBarButtonItem *item = (self.navigationItem.rightBarButtonItem == nil) ? self.popoverBarButtonItem : nil;
[self.navigationItem setRightBarButtonItem:item animated:YES];
}
You could conditionally keep or remove the button in -viewDidLoad the same way. The segue remains anchored to the UIBarButtonItem.
I'm gonna try making a dummy view that's the size and shape of the views I want to present the popover from, wire that to the segue popover target, and then move the view to the right position in prepareForSegue:sender:
I'm not sure this is exactly what you want, but this is what I would do. Create a button and set it up with some target/action. Call that target/action method
presentPopover:(UIButton *)sender;
Then in the presentPopover method, say
UIViewController *customAdditionalViewController = [[MySpecialVC alloc] init];
//Configure the customAdditionalViewController
//Present the customAdditionalViewController in a Popover
UIPopoverController *popover = [[UIPopoverController alloc] initWithViewController:customAdditionalViewController];
//Set popover configurations
[popover presentPopoverFromRect:sender.frame inView:self.view permittedArrowDirections:/*whatever you want*/ animated:YES];
That is how I would handle your use case.

Resources