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
Related
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.
I have an application that is supporting only ios7+ The navbar setup is using the new 64px high bar that appears beneath the status bar. Here is what it looks like when the app launches:
If I do any sort of "presentViewController", when i dismiss the view the navbar shifts back to 44px height and still appears underneath the status bar which in-turn makes all the contents of the view also shift up. Here is what that looks like:
It doesn't matter if I am presenting one of my own views or if I simply present a UIImagePickerView, any sort of slide up modal via the navigation controller breaks the navbar setup. Any ideas on how to fix this?
A few notes:
in plist: "View controller-based status bar appearance" is set to "NO"
navbar configured with self.navController.navigationBar.translucent = NO;
I am using .xib NOT Storyboards
UPDATE:
I have the navigation controller inside of a PKRevealController (https://github.com/pkluz/PKRevealController). Taking the reveal controller out and just adding the nav controller to the window itself fixes the issue... why would the reveal controller cause it to behave differently?
SOLUTION:
It turned out that the PKRevealController library was causing the issue. I reworked how it was set up in the AppDelegate and that solved the problem, although it's sorta of "hacky". I put my "before" and "after" configurations below:
the initial setup was :
configure PKRevealController
configure NavController and add rootView
set pkreveal front view = navController
add reveal controller to window as windows root view
the fix is
create a containing NavController
do stpes 1-3 above
add pkrevealcontroller to the containing navController
set containing nav controller nav bar to hidden
add containing nav controller to window as root view
If its navigationcontroller than you can use this inside every viewcontroller's viewdidload:
if ([self respondsToSelector:#selector(edgesForExtendedLayout)]) {
self.edgesForExtendedLayout = UIRectEdgeNone;
}
I had the same problem, but solved very easily by just setting the UINavigationBar Y-position to 20px and not to 0px. Then you have to assign the UINavigationBarDelegate to your ViewController:
[_navigationBar setDelegate:self];
Furthermore you have to add this method to your ViewController, which will be called because of the Delegate assignment:
-(UIBarPosition)positionForBar:(id<UIBarPositioning>)bar
{
return UIBarPositionTopAttached;
}
I've hit a weird problem with UITabBarController on iOS7 and can't seem to find a workaround, so any help would be welcome!
Scenario:
Navigation-based app using landscape orientation on iPad.
App consists of a main view, and a second view which is a UITabBarController.
TabBarController has two tabs.
First view has two buttons - each button performs a segue to the tab bar controller and sets a different tab as selected. (i.e. button1 selects the first tab, and button2 selects the second tab).
Setting the tab is done in prepareForSegue by calling setSelectedIndex on the tab bar controller.
Outcome:
On iOS 7 I am finding that the view shown in the tab bar controller fails to register any touch events along the right-hand edge of the view! So in the storyboard shown above, the UISwitch on the right side of the screen cannot be tapped.
I've even attached a tap gesture recognizer to the views and used it to log the area of the screen that can be touched - it seems to register touch events up to about x=770 points across. The remaining 1/4 of the screen is 'untouchable'!
After the segue, if you manually switch to the other tab and switch back again, the touch events are 'fixed' and the full view responds to touches again.
This doesn't seem to be a problem on iOS 5 / 6.
Any help much appreciated as to:
What is causing this to happen in the first place (iOS7 bug / change?)
How else can I work around this? I've tried calling setSelectedViewController as well as using setSelectedIndex and this seems to be the same.
Thanks in advance.
I ended up raising this with Developer Tech Support, and it looks like a bug. This is the response I got back from Apple:
The container view that the tab bar controller sets up to contain your view controller is not being resized to account for the interface being in landscape orientation. It's dimensions at the time your view controller is displayed are 768 (width) x 1024 (height).
The view hierarchy looks like this when the selected tab's view is displayed:
UIWindow
/* Navigation Controller */
UILayoutContainerView
UINavigationTransitionView
UIViewControllerWrapperView
/* Tab bar controller */
UILayoutContainerView
UITransitionView
UIViewControllerWrapperView <-- Incorrectly sized.
/* MyViewController */
MyViewController.view
The incorrect size of UIViewControllerWrapperView does not cause a display problem because subviews are still displayed even if they are outside their superview's bounds. However, event routing is much more strict. Events on the right quarter of the screen are never routed to your view controller's view because the hit test fails at the wrongly-sized UIViewControllerWrapperView where the event falls outside UIViewControllerWrapperView's bounds.
As a workaround, I subclassed UITabBarController, and added the following in viewWillAppear:
#implementation FixedIOS7TabBarController
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
// Fix the frame of the UIViewControllerWrapperView
self.selectedViewController.view.superview.frame = self.view.bounds;
}
#end
Hope that helps someone else....
As explained in this answer,
The container view that the tab bar controller sets up to contain your
view controller is not being resized to account for the interface
being in landscape orientation. Its dimensions at the time your view
controller is displayed are 768 (width) x 1024 (height).
I was encountering this problem when the TabBarController was originally displayed in portrait mode. When the device was rotated into landscape mode, the view was unresponsive on the right hand side.
The solution proposed in that answer did not work for me, because viewWillAppear: is invoked only once. However, viewDidLayoutSubvews is invoked whenever the view changes, including rotations, so my solution was to subclass UITabBarController and perform the workaround in viewDidLayoutSubvews:
#implementation FixedIOS7TabBarController
- (void)viewDidLayoutSubviews
{
// fix for iOS7 bug in UITabBarController
self.selectedViewController.view.superview.frame = self.view.bounds;
}
#end
End up finding a workaround here:
self.view.autoresizesSubviews = YES;
self.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
Right answer don't worked for me, cause user can change orientation; And it still not touchable in some area when change orientation.
So I create my own solution, I don't sure that is normal solution.
#implementation FixedIOS7TabBarController
- (UIView*)findInSubview:(UIView*)view className:(NSString*)className
{
for(UIView* v in view.subviews){
if([NSStringFromClass(v.class) isEqualToString:className])
return v;
UIView* finded = [self findInSubview:v className:className];
if(finded)
return finded;
}
return nil;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
UIView* wraperView = [self findInSubview:self.view className:#"UIViewControllerWrapperView"];
wraperView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
#end
Works perfectly for me!
In the list of view controllers on the left hand side navigate to the views/view controllers affected, drag the view to underneath the first responder so that it is disassociated to the view controller's view.
Then go to the layout tab on the right hand side, select all 4 anchors and both sets of resizing arrows (horizontal + vertical).
Then drag the view back to where it was originally (just below the view controller).
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 am trying to programmatically add a Navigation Controller to my View based Application. This is the code I am using (this code gets called after a button press in a view controller):
MainMenu *control = [[MainMenu alloc] initWithNibName: #"MainMenu" bundle: nil];
UINavigationController *navControl = [[UINavigationController alloc] initWithRootViewController: control];
[self.view addSubview:navControl.view];
[control release];
That works, but this ends up happening:
Notice the odd margin above the Navigation control.... My View controller that I am adding the Navigation Controller to has a gray background which you can see.
Any ideas??
If you have a better way of adding a Navigation Controller to a View based Application I am very open to suggestions!
Thank you in advance!
Thank you both for your response, but unfortunately, wantsFullScreenLayout set to YES or NO in the code didn't have any effect. I was able to push the Navigation Controller up by 20 using this line of code:
self.navigationController.navigationBar.frame = CGRectOffset(self.navigationController.navigationBar.frame, 0.0, -20.0);
but then what happened was that the View Controller did not move up with the Navigation bar and left a gap below the Navigation Bar and the View Controller. What eventually worked was checking the Wants Full Screen checkbox in IB in the MainWindow view controller that is automatically generated when you set up a view based application.
The gap you are seeing is the same height as a status bar. Check the status bar settings in your NIB file.
Chances are you want to make the UINavigationController the root view controller for the window, rather than whichever view controller you have now. That would be the better way to do it.
The reason you're seeing that extra margin at the top is because UINavigationController normally expects that it will be sized to fill the entire screen (except perhaps a tab bar at the bottom, if it's inside a UITabBarController), and therefore expects that the top edge of its view will be under the status bar if the status bar is visible. Therefore, it places its navigation bar 20 pixels below the top of its view to leave space for the status bar, without bothering to check whether its view actually is under the status bar. Interestingly, sometimes a re-layout operation will perform this check, but that's unreliable. What I've found works well in a situation like this is to set the UINavigationController's wantsFullScreenLayout property to NO. Then ti doesn't try to leave room for the status bar, so everything works as expected.
I've been struggling with this same issue this morning. Since setting the wantsFullScreenLayout property doesn't seem to have any effect, I resorted to using a little subclass, which worked fine:
#interface MyNavigationController : UINavigationController
#end
#implementation MyNavigationController
- (BOOL)wantsFullScreenLayout;
{
return NO;
}
#end
Its so simple to remove that gap..
self.navigationBar.view.frame = CGRectMake(0, -20, 320, 480);