Applying bar metrics on a standalone UINavigationBar - ios

My app has a view controller that due to the fact it plays its own custom transition animations, provides its own standalone UINavigationBar view at the top (As opposed to using a UINavigationController).
When using an iPhone, and when rotating the device, I would like the UINavigationBar to automatically apply the landscape UIBarMetrics properties (eg, change height, change the background image, resize the buttons etc), but by default, it does not. This is a problem on iOS 7, since even if I manually change the height of the UINavigationBar, the UIBarButtonItem elements don't change their vertical positions.
Is there a way to manually 'tell' the UINavigationBar to apply specific bar metric properties to itself? Or is that actually an implementation inside UINavigationController, and not UINavigationBar?

After various testing and trial and error after asking this question, I eventually worked out a solution that fixed all of my issues, so I'll post it here under the solution I'd previously accepted.
When my app is displayed in landscape on an iPhone, I wanted the UINavigationBar at the top to shrink to the standardly accepted 32 points high, as is the case with any apps that use the UINavigationController class. However, as I am not using a UINavigationController for this particular view (for varying reasons of feasibility), I needed to implement this manually.
To account for the new transparent status bar in iOS 7, I adjusted the origin and size of the UINavigationBar so it encompassed both the bounds of the status bar, and the normal UINavigationBar region (ie, so the UINavigationBar frame origin was {0,0}, and the height was 52 points.)
Unfortunately, this happened:
While the bar itself is rendering at the proper position and height, all of the content in the bar, including the title and buttons are not positioned properly, being much too high, almost touching the status bar content.
It was pretty obvious what was happening. The navigation bar content is being vertically aligned to its own middle, completely disregarding the presence of the status bar content.
When I tested the same orientations with a normal UINavigationController, this was not the case, and the title and buttons in the UINavigationBar from the UINavigationController worked absolutely fine. Apple had done SOMETHING in there that wasn't part of the normal UINavigationBar implementation.
Going on this, I picked apart the view layout hierarchy of a UINavigationControllerto see what was happening to the UINavigationBar in there (Mainly calling a lot of NSLog() statements that would dump the subviews of the navigation bar.)
This is what I discovered:
From the looks of it, Apple have employed a relatively sneaky hack to achieve this effect. It turns out the actual UINavigationBar is actually placed right below the status bar (ie at point {0,20}) and only has a height of 32 points. Then, what happens is a private subview inside the UINavigationBar in charge of rendering the background is extended upwards, outside of the bounds of the navigation bar to encompass the region behind the status bar (ie, its origin is {0, -20}, and its height is 52 points, local to the navigation bar's subview coordinate space).
So by doing that, not only does the content vertically align properly, but the translucent effect still extends behind the status bar.
Anyway, after I discovered this, it was pretty straightforward to write a solution. All I needed to do was reposition and resize the UINavigationBar back to how I had it iOS 6 (ie, 20 points down, and only 32 points high), and then implement a UINavigationBar subclass that override the layoutSubviews method, grabbed the internal background view (Doing a quick subview check for a view with a class name that matched "Background"), and then manually extended it.

The bar metrics properties you can set on a UINavigation bar are things like background image and the title vertical position. Heigh and width need to be set from within your view controller.
If you need to manually tell the navigation bar to change it's size when the orientation changes you can implement the method - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration1 in your view controller and change the size there.
Another option you can use is to use autolayout to specify that the width of your navigation bar is pinned to the left and right sides of its superview and let it figure out how wide it should be. For example
UINavigationBar *bar = [[UINavigationBar alloc] init];
bar.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:bar];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[bar]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(bar)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[bar(44)]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(bar)]];

Related

Add a UINavigationBar to a UIView using Autolayout

I have a modal view that I'm presenting over the current view that has a UINavigationBar as a subview. Everything's working great, except that the UINavigationBar doesn't want to behave according to the autolayout constraints. Everything else in the view behaves as desired.
For some reason, the left and right bar button items are cut off on the edges of the view. I inspected the view at runtime to reveal that the total width of the UINavigationBar is the right width, but the bar buttons aren't conforming.
I also checked to make sure I didn't have negative margins or some other anomaly in the constraint definitions.
I have also tried setting the frame of the UINavigationBar manually, but it yielded no improvement.
Problematic View
Constraints
I'd like to keep the UINavigationBar, because it gets styled globally with the rest of my app (as opposed to creating a custom UIView imitation and styling it individually). Does anyone know what handle to jiggle to get the bar button items to conform to the right width?

MKMapView Content Insets with Navigation Bar

I'm experiencing a strange issue with MKMapView placed in a view with a navigation bar. Given a UIScrollView in the same situation, whose width, height, and center are constrained to it's superview (the view controller's view object) the insets will be adjusted if the controller's automaticallyAdjustsScrollViewInsets property is set to YES, and not if set to NO. That's great. I can not find this documented anywhere, but the map view seems to do the same thing, but it's not possible to disable it. Setting automaticallyAdjustsScrollViewInsets has no effect.
In brief(ish):
In the image below, the small back square is centered within the view, regardless of navigation bar. The map view is also constrained to be exactly the size of the view (it extends behind the nav bar). The location of the map view was set to be the current location (also indicated by the blue circle) so I would expect them to match, but they are off by half the combined height of status and navigation bars.
Is there an explanation for this behavior, and a way to get it to work as expected?
This is very strange because an MKMapView is not a subclass of a UIScrollView but it is behaving like one in this case. That's not completely unreasonable, but I would expect the auto adjusting insets to work consistently on a map view and a scroll view.
Workaround
Something awful like this would 'solve the problem':
MKCoordinateRegion region = MKCoordinateRegionMake(self.coordinate, MKCoordinateSpanMake(0.008, 0.008));
MKMapRect mapRect = MKMapRectForCoordinateRegion(region);
[self.mapView setVisibleMapRect:mapRect edgePadding:UIEdgeInsetsMake(0, 0, 64, 0) animated:YES];
Either adjusting the bottom inset as well, or adding a negative inset on the top gets things lined up, but it would break if this behavior of the map view changes, or if the view controller is presented without a navigation bar, or if there is a stiff breeze. It also erodes my confidence in pulling correct coordinates back out of the map view.
MKMapView does indeed sit under the navigation bar, but automatically uses a housing view controller's topLayoutGuide to shift annotations and the compass view, etc. down from under it.
You should make sure that your own views take this into account in their autolayout code.
If you take a look at this, you can see that the mapview is very likely not sitting under the navigation bar, as you think. It is sitting between the bottom of the nav bar and the bottom of the superview.
On another note, the navigation bar is translucent by default, so if the mapview was really sitting under the nav bar you would be able to see through it, assuming you didn't disable the default nav bar behavior.

Animate Controls down with change to UINavigationBar

I have a UINavigationController with standard UINavigationBar. When presenting certain UIViewControllers and orientations, the UINavigationBar may or may not appear and it may or may not have a prompt element. This means that the bar height changes frequently.
I have some subviews below the UINavigationBar set with NSLayoutConstraints to topLayoutGuide. It generally lays out as expected, adjusting vertical position of the subviews appropriately based on the height of the UINavigationBar. What it does not do is move the subviews at times when the UINavigationBar is animated after the view is already displayed.
Specifically, coming from a state with UINavigationBar hidden, transition to a UIViewController which does not hide the navigation bar to one which does. The view displays, then navigation bar animates into place. The subviews do not move down. If I rotate the device, every things lays out appropriately again. Only when animating the navigation bar in and out or to display/hide the prompt I not find a hook to reevaluate the constraints.
I tried [self.view updateConstraints] and [self.view updateConstraintsIfNeeded] in various places such as viewDidAppear, viewDidLayoutSubviews. Nothing seems to update that topLayoutConstraint.
I am familiar with edge restraints, translucent navigation bar and other various methods of keeping the entire view from appearing under the navigation bar. I do want to keep view full size and I want the translucent bar so these are not solutions for me. It seems the constraints should handle this automatically, hence the "auto" in auto layout.
To simplify, for recreation, UINavigationController with rootViewcontroller showing normal navigation bar with just a title. In viewDidLoad of the next presented viewController I have [self.navigationContoller setPrompt:self.myPrompt]. The view is presented, when the prompt is set, the navigation bar grows larger. Some labels below the bar are set with relation to topLayoutGuide, which places them correctly initially. I expect they would move down when the bar grows. Rotate device back and forth, they now layout correctly. Pop the viewController and push back to top, repeats as above.
So, it turns out it was all me. After trying all manner of forcing layout updates in all sorts of ways, the solution was to move the [myView setPrompt:myPrompt] out of viewDidLoad and call it in viewDidAppear instead.
Works completely as expected. Navbar grows, subviews shift and shrink as needed. Now I have to hunt down all the experimental code I plastered everywhere trying to do it wrong.

Any quick solution to make a view appear behind the status bar in iOS 7?

I'm porting my app from iOS 6 to IOS 7 (there will eventually be a complete GUI redesign for iOS 7 but in the meanwhile just getting the existing GUI to display properly on iOS 7 is the goal.
I had the issue where the status bar was overlapping my GUI and so have shifted the Y origin of the view controller's view down by 20.
However my app contains a pulldown which when retracted is overlapping with the status bar. In the screenshot the red is a button which is present in the pulldown view. The grey bar is the top of the main view behind which a portion of pulldown is hiding when retracted.
I implemented the pull down as a fixed size child subview of the main view and when retracted its Y origin is a negative number thus it is effectively still displayed but off the top of the screen. When the user pulls it down I just animate the increase in the Y origin until eventually the origin is 0.
Is there some way I can make the pull down view appear beneath the status bar or some other quick solution?
Note of course I can't simply toggle the pulldown's alpha to display/hide it as it pullsdown obviously thus its appearance/disappearance is not a discreen action. I could maybe attempt to make the portion of it that is on top of the status bar invisible but as its something that is moving that seems like its going to be complicated. Is there any simple solution?
Thanks
Add another view, with a fixed position, under the status bar (with the same color of your grey bar), 20px tall and same width of the status bar, but with a z-index higher than the retracting view. This view will cover the retracting view (but not the status bar) acting as a "background" for the status bar itself. Obviously you have to adjust the Y position of the retracting view to make it tappable by the user (but under the status bar)
iOS 7 by default lets views take up the fullscreen, including the status bar. This is controlled using the UIRectEdgeAll property.
If you want the old style view, set UIRectEdgeNone for self.edgesForExtendedLayout
in viewDidLoad:
self.edgesForExtendedLayout = UIRectEdgeNone;

UIToolbarPosition set to bottom on resize

I have a UIToolBar at the top of one of my views and the shadowing shows up on the bottom of the tool bar as I'd expect. I know UIToolbarPosition is internally set to UIToolbarPositionTop.
When I rotate my device (iPad), the tool bar grows (using auto sizing in IB) and the UIToolbarPosition changes to UIToolbarPositionBottom which flips the shadow to be at the top of the bar. If I don't have the bar resize, it remains correct, but of course doesn't stretch to match screen width.
So, what gives? What would cause the UIToolbarPosition to flip on me? This is sitting at 0,0 the whole time and only changes width based on rotation.
Extra: I've considered work arounds like using a UINavigationBar. UIAppearance is probably a no go because I do have a toolbar at the bottom too and I do want my "top" different than my "bottom"
I eventually solved this using the UIBarPositioning protocol on UIToolBarand setting the barPosition = UIBarPositionTop

Resources