I created a simple project to demonstrate this strange behavior. I created all of this in the interface builder without touching the code so that it's easy for you to reproduce.
The start page is embedded in the navigation controller containing only one button, and when clicking on it, the screen will be directed to the tab views (with navigation bar on the top).
There are 2 strange things happening when running the program.
1) The tab item2's table view is underneath the navigation bar where as the item1's is displayed correctly.
vs.
As you can see, although the two table view is placed in the exact same position, when the program runs, the second one even hides the search bar because of the incorrect alignment.
2) When clicking on the search bar in tab item1, the navigation bar is not hidden as the default behavior (where the nav bar hides and the search bar shows instead).
Since the nav bar is semi translucent, I can see the 'Cancel' button underneath the nav bar.(Any one with a good eye can see the blue tint at the upper right corner of the screen shot). It means the search bar goes to the correct location but the nav bar just doesn't go away.
Also, note that the table view is not shifted up with the search bar(again, default behavior) when the search bar is active.
PS: I am using XCode 6. Also I tried adding constraints to the tableviewcontrollers, but no dice.
Any ideas on this? Thanks in advance.
As logixologist suggested, you can put a navigation controller for each tab as the first controller of the tab. If you don't want to do that, you can add the following code in viewDidLoad of second item view controller.
CGFloat shiftDist = CGRectGetHeight(self.navigationController.navigationBar.frame) + CGRectGetHeight([UIApplication sharedApplication].statusBarFrame);
self.tableView.contentInset = UIEdgeInsetsMake(shiftDist,0, 0, 0.0f);
self.tableView.contentOffset = CGPointMake(0, -shiftDist);
After that, there is problem for rotation. You'll notice if you rotate to landscape, the search bar doesn't stick under the navigation bar. Here is how to fix it.
- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id <UIViewControllerTransitionCoordinator>)coordinator NS_AVAILABLE_IOS(8_0);{
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
CGFloat heightBeforeRotation = CGRectGetHeight(self.navigationController.navigationBar.frame) + CGRectGetHeight([UIApplication sharedApplication].statusBarFrame);
CGFloat offsetY = self.tableView.contentOffset.y;
[coordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
} completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
{
CGFloat heightAfterRotation = CGRectGetHeight(self.navigationController.navigationBar.frame) + CGRectGetHeight([UIApplication sharedApplication].statusBarFrame);
self.tableView.contentInset = UIEdgeInsetsMake(heightAfterRotation,0, 0, 0.0f);
self.tableView.contentOffset = CGPointMake(0, heightBeforeRotation + offsetY - heightAfterRotation);
}];
}
The issue is how the Tab Bar Controller is presented. Tab Bar Controllers are special and generally should be the initial view controller in your storyboard.
If the Tab Bar Controller is not the initial view controller then it should be presented modally. In my test app I changed the segue to "Present Modally".
Then added a Navigation Controller for each Tab bar Table View Controller with a done button that dismissed the modal Tab Bar Controller.
- (IBAction)DoneButton:(id)sender {
[self.parentViewController dismissViewControllerAnimated:YES completion:nil];
}
Per Apple's Documentation "View controller Catalog for iOS"
https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/ViewControllerCatalog/Chapters/CombiningViewControllers.html
Adding a Navigation Controller to a Tab Bar Interface
An app that uses a tab bar controller can also use navigation
controllers in one or more tabs. When combining these two types of
view controller in the same user interface, the tab bar controller
always acts as the wrapper for the navigation controllers.
The most common way to use a tab bar controller is to embed its view
in your app’s main window. The following sections show you how to
configure your app’s main window to include a tab bar controller and
one or more navigation controllers. There are examples for doing this
both programmatically and using Interface Builder.
Tab bar views do not support translucency and tab bar controllers
never display content underneath their associated tab bar. Therefore,
if your navigation interface is embedded in a tab of a tab bar
controller, your content may underlap the navigation bar if you adopt
a full-screen layout as described in Adopting a Full-Screen Layout for
Navigation Views, but it does not underlap the tab bar.
When embedding navigation controllers in a tab bar interface, you
should embed only instances of the UINavigationController class, and
not system view controllers that are subclasses of the
UINavigationController class. Although the system provides custom
navigation controllers for selecting contacts, picking images, and
implementing other behaviors, these view controllers are generally
designed to be presented modally. For information about how to use a
specific view controller, see the reference documentation for that
class.
Displaying a Tab Bar Controller Modally
It is possible (although uncommon) to present a tab bar controller modally in your app. Tab bar interfaces are normally installed in
your app’s main window and updated only as needed. However, you could
present a tab bar controller modally if the design of your interface
seems to warrant it. For example, to toggle from your app’s primary
operational mode to a completely different mode that uses a tab bar
interface, you could present the secondary tab bar controller modally
using a crossfade transition.
When presenting a tab bar controller modally, you always pass the tab
bar controller object as the first parameter to the
presentModalViewController:animated: method. The tab bar controller
must already be configured before you present it. Therefore, you must
create the root view controllers, configure them, and add them to the
tab bar controller just as if you were installing the tab bar
interface in your main window.
As with all other modally presented view controllers, the parent view
controller is responsible for dismissing its modally presented child
view controller in response to an appropriate user action. When
dismissing a tab bar controller though, remember that doing so removes
not only the tab bar controller object but also the view controllers
associated with each tab. The view controllers that are not visible
are simply removed, but the view controller displayed in the currently
visible tab also receives the usual viewWillDisappear: message.
For information on how to present view controllers (including
navigation controllers) modally, see Presenting View Controllers from
Other View Controllers in View Controller Programming Guide for iOS.
For information on how to configure a tab bar controller for use in
your app, see Tab Bar Controllers.
Related
I have a view controller inside a tab bar controller (the view controller is item index 2). When I push a modal (camera picker) from the view controller, when it dismisses it always returns to the first view controller in the tab bar controller (item index 0). Now I COULD set the tab upon completion of dismissal, but that creates an ugly "flash effect" where it shows the first view controller for about a milisecond before going back to the right one. It's quite ugly from a design standpoint. How can I fix it?
Here's what I have now that is ugly:
[picker dismissViewControllerAnimated:NO completion:^{
UINavigationController * nav=self.navigationController;
RootTabBarController * root=(RootTabBarController *)nav.parentViewController;
[root showProfilePage]; //this calls setSelectedIndex in the tab bar controller
}];
I accidentally set SetSelectedIndex=0 in the tab bar controller view did appear.
I have a paged, horizontal PageViewController used for a swipe-based interface.
In one of these views is a Navigation Controller which segues to a second detail view.
When the second view appears I would like to hide the parent view's Navigation Bar but I don't know how to access any method on the parent view.
I imagine it would look something like this in the detail view
-(void)viewWillAppear {
[parentViewClassNameHere setNavigationBarHidden:YES];
}
How would one go about setting up an accessible method on the parent?
The Navigation Bar is actually owned by the UINavigationController where both the parent view controller and your second view controller are pushed on. So if you hide the navigation bar from your second view controller, it means you are also hiding the bar from the parent view controller.
You can hide the navigation bar with
[[secondDetailViewController navigationController] setNavigationBarHidden:YES animated:YES];
If you still want to have an accessible method on the parent, see this post for different ways to have an instance to the parent view controller. Then you can call any public method on the instance.
I've created a UIViewController subclass called addItemToListViewController. I selected add an "xib" as well, and just created a simple page with a couple of labels and a textField. In the interface builder I selected "Top Bar - Navigation Bar" so that when it is put on the stack when the application runs it will have a top bar that will match the initial main window. In the Interface builder it shows the top border, but when I run the application in the simulator the top bar is not present once the view is displayed.
Here is the code I placed in the rootViewController to present the view controller
- (IBAction)addButtonPressed:(id)sender
{
AddItemToListViewController *addItemToListViewController = [[AddItemToListViewController alloc] initWithNibName: #"AddItemToListViewController" bundle:nil];
[self presentModalViewController: AddItemToListViewController animated: YES];
[AddItemToListViewController release];
}
I'm only able to have the top bar present if I manually add a Navigation bar to the xib. If I must add a Navigation bar to my xib, what is the purpose of the "Top Bar" attribute?
- (IBAction)addButtonPressed:(id)sender
{
AddItemToListViewController *addItemToListViewController = [[AddItemToListViewController alloc] initWithNibName: #"AddItemToListViewController" bundle:nil];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:addItemToListViewController];
[self presentModalViewController: navController animated: YES];
[AddItemToListViewController release];
[navController release];
}
That "top bar - Navigation bar" in InterfaceBuilder is what's known as a "Simulated Metric". It's there to help you lay out your view with correct spacing when other visual elements - the status bar, navigation bar, or tab bar - might consume some of the device's screen real estate. It doesn't actually do anything other than shrink the vertical dimensions of the view defined by the NIB. The purpose is to help you layout your view, not to actually create a component that will appear in your app.
If you want a navigation bar, then you have two choices. The first choice is to use a navigation controller (of which your initial view will have to be the root) and call
[self.navigationController pushViewController:newVC animated:YES];
The process of setting up a navigation controller correctly, etc, is nontrivial, and you should do some searching to find the best way to do that for your app. For a simple app, especially if you're just learning iOS, you can use the "Navigation-based Application" template when you create a new project. With a navcon, you get all the fancy behavior normally associated with that top bar - an automatic back button, fancy left/right scrolling when you transition to a detail view, etc.
The second option is to put a "fake" navigation bar in the detail view, using the Navigation Bar object. You can find that object, plus some other related objects, in the bottom half of the "Utilities View" (the right-most pane) in XCode. Just drag the object into your XIB and blammo, you have a 44-pixel tall gray bar. This navigation bar is just like what you get when you use a Navigation Controller except you don't get the stack functionality; you can still add buttons to the left and right, change the title, tint it to a specific color, etc.
The xib does not know you will use the controller as a modal view as it could also be used for a normal view which could show a top bar. Only when you push the view it will use or ignore the showing of this top bar.
In short: its there in case you will use the xib for a normal view :)
I have a "landing page/view" that I dont want the navigation bar to show, so I turn it off during the viewDidAppear
navigationBarHidden = YES;
When i push a view on the stack and then move it off. the main landing page shows the nav bar then hides it which cause a flicker that I dont want.
is there a way to have the landing page be a UIView or something? When a menu item is touched the app would push a new view on top of the default landing page. It sound like it would be hard to do without having the landing page be a UINavigationController. Any thoguhts?
Try hiding the navigation bar in viewWillAppear, rather than viewDidAppear.
If you don't need to go back to the landing page, use a view controller for the landing page and present it modally from the navigation controller when the application starts.
So you do want to go back to the landing page.
It's hard to accomplish that with UINavigationController. Suppose your are going back to the landing view. While the transition, the old view should have a navigation bar, and the new view (landing page) should not have a navigation bar. UINavigationController does not allow you manually modifying the transition animation. In other words, you cannot animate hiding/unhiding the navigation bar along with push/pop animation (using viewWillAppear doesn't solve the problem).
So what would I do, if I really, really need this?
I would have a rootViewController (of UIViewController), whose view is the only subview of your application window. When your application starts, rootViewController add the landing view as a subview of its view. When the user selects an item there, you create an UINavigationController with the corresponding view controller as its root view controller.
And, using CATransition animation with type of kCATransitionPush and subtype of kCATransitionFromRight, you add the view of the navigation controller as a subview of rootViewController's view.
Then you need a 'back' button for the first view of the navigation controller. In all view controllers that are the first level view controllers of the navigation controller, create a bar button item with a text 'Back', and add it to their navigationItem.leftBarButton property. Set a target-action (probably to the rootViewController) pair for the button.
When the action message fires, use CATransition animation (now with kCATransitionFromLeft subtype), to remove the current navigation controller's view from rootViewController's view.
The transition may not look as perfect as the native UINavigationController, but I believe this is the best you could get.
Actually the way to do this is to implement UINavigationController's delegate method navigationController:willShowViewController:animated. This method is where you should handle hiding and showing your navigation bar so the animation will occur during the push/pop animation.
I came across a method that is simple and works well for me, and is not given here yet. I assume you have a view controller for the main landing page and it is set as root view controller of the navigation controller. Then you should hide/show the navigation bar in the viewWillAppear and viewWillDisappear methods of the main landing page controller:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:YES animated:YES];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.navigationController setNavigationBarHidden:NO animated:YES];
}
Source:
http://www.iosdevnotes.com/2011/03/uinavigationcontroller-tutorial/
I'm creating a split view controller app, the detail view has a segmented control in a navigation bar at the top. Clicking on a segment will add a new view to the detail view with the appropriate information on it (covering up the DetailViewController's default UIView).
I've created two new UIViews, corresponding to each segment, and I'm trying to add them to the view like this (in DetailViewController.m):
if (exerciseSegmentControl.selectedSegmentIndex == UISegmentedControlNoSegment) {
NSLog(#"No segment selected");
}
UIView *viewToShow;
if (selectedView == 0 && exerciseSegmentControl.selectedSegmentIndex == 1) {
viewToShow = exerciseSolutionView;
}
else {
viewToShow = exerciseView;
}
[self.view addSubview:viewToShow];
I see the view appear, but it's in the wrong place, it is placed at the very top of the window, instead of below the navigation bar.
In IB, I've created instances of the views, and I've used the Attributes inspector to specify "Navigation Bar" for top bar, which sets the height of the view correctly. But the view is clearly being added too far up in the window - I see the view below it (the DetailViewController's UIView) peaking out at the bottom (I changed the background color so I know which view I'm seeing).
Any tips on how to get the subview I'm adding to get placed correctly in the window?
Thanks!
probably you might want to add Navigation Bar in IB (or in codes) manually.
setting Navigation Bar in the top bar settings doesn't really add a Navigation bar nor reserve some space for it.
anyway.. what i normally do is to add 2 views in IB, then stack them properly.
then link them wif IBOutlet.
That will most probably solve your problem