Auto Layout and in-call status bar - ios

I'd like to ask about Auto Layout and in-call status bar. Here's a simple scenario that demonstrates my problem:
Create project with "Use Storyboards" enabled
Add "View Controller" and enable its "Is Initial View Controller"
Set background color of controller's view to red
Add "Table View" into controller's view
The table view should have 4 layout constraints (leading, top, trailing, bottom) to Superview with constant set to 0.
Now when I run this app in Simulator and press ⌘ + T I can see red background while the in-call status bar animates in. Is it possible to get rid of this glitch?

(Using answer instead of comment due to lack of reputation, sorry.)
I ran into this issue as well and was trying out e.g. the solution pointed out above: It didn't work for me.
So I created a repository with example code to expose the original poster's problem. There are example applications for these scenarios:
the Custom View Controller is the window's root view controller,
the Custom View Controller is a child of a UINavigationController which is the window's root view controller,
the Custom View Controller is a child of a UITabBarController which is the window's root view controller and
the Custom View Controller is a child of a UINavigationController which is as child of a UITabBarController which is the window's root view controller.
It turned out that the solution from CEarwood actually works… when the custom view controller is a child of a UINavigationController (cases 2 and 4). Hoewever, it does not work in cases 1 and 3.
I hope this information is useful.

For a purely Auto Layout answer you can get a reference to the bottom constraint and adjust its constant when UIApplicationWillChangeStatusBarFrameNotification is received and back to 0 when the DidChange notification is received. Here's the test VC I used:
#interface CEViewController ()
#property (nonatomic, strong) IBOutlet NSLayoutConstraint *bottomConstraint;
#end
#implementation CEViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarFrameWillChange:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(statusBarFrameDidChange:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
}
- (void)statusBarFrameWillChange:(NSNotification *)note {
NSValue *newFrameValue = [note userInfo][UIApplicationStatusBarFrameUserInfoKey];
self.bottomConstraint.constant = newFrameValue.CGRectValue.size.height;
[self.view setNeedsLayout];
}
- (void)statusBarFrameDidChange:(NSNotification *)note {
self.bottomConstraint.constant = 0;
[self.view setNeedsLayout];
}
#end

This is an effect from the screen resizing.
When the in-call status bar appears, the view resizes to the size it should have with the in-call status bar active and then moves down as the status bar changes size.
For a brief moment, the view under the table view is visible. What you could do is add a view under the table view extending downwards out of the screen to cover-up the background color.
Another approach is with your AppDelegate, implement:
-application:willChangeStatusBarFrame:
and resize the table view to cover the bit that gets exposed. Then when -application:didChangeStatusBarFrame: gets called, resize it back to the original size.

Related

hideBottomBarOnPush not working on child views

Pushing using segues with parameter on performSegues will hide the bottom bar when hidesBottomBarOnPush is set to true, the problem is on the child view of the view that has been hidden you cannot show/unhide the bottom bar. Already tried hidesBottomBarOnPush = false. Is there any way to unhide the bottom bar when the parent view's bottom bar is hidden.
Edit:
If I use the tabBar.hidden a small white rect will be shown at the bottom of the view. And also another problem with that is, when I change to another tab then go back to the tab I'm working on, the child view is retained but tabBar becomes hidden.
Legend:
3rd view controller - is the parent view that push segues.
4th view controller - is the child view.
Hoping someone can help me with this problem.
if you want to hide the bottom bar in one particular view controller,and show in others, try this, i think this works better than hidesBottomBarOnPush
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.tabBarViewController.tabBar setHidden:YES];
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear];
[self.tabBarViewController.tabBar setHidden:NO];
}

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.

Black area with interactivePopGestureRecognizer when popping a view controller with visible nav bar to a one with hidden nav bar

I have this ViewController #1 which is the root view controller of a navigation controller and has
self.navigationController.navigationBarHidden = YES;
ViewController #1 tells its navigation controller to push ViewController #2, which has
self.navigationController.navigationBarHidden = NO;
When I want to go back from ViewController #2 to ViewController #1 by swiping from the left side of the screen, I see my views as the screenshot I attached here. This is captured as I move my finger to the right, so as I keep swiping to the right, the black area on the top right gets smaller and smaller until ViewController #1 covers all the screen area.
I'm guessing that this is caused by the hidden/visible navigation bar difference between the two view controllers.
I'd like to learn if it's possible to get rid of this black area.
As discussed with HoanNguyen, I had put my code to hide/show the navigation bar on viewWillAppear/Disappear but finally I figured out that the trick was to set the values animated. Weird, but this solved my problem and the black area is now gone:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.navigationController setNavigationBarHidden:self.shouldHideNavBar animated:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.navigationController setNavigationBarHidden:!self.shouldHideNavBar animated:animated];
}
You should put your code set hidden/shown navigation in viewWillAppear or viewDidAppear.

UIViews ending up beneath tab bar

In my app, I aligned a label the standard amount from the bottomLayoutGuide using autolayout. When the app first starts everything is layed out as I wanted but when I switch tabs and go back the label has disappeared under the tab bar controller.
If I rotate the device, the landscape view appears correctly and when I rotate it back to portrait the view is back to normal. I can't seem to figure out what is causing this behavior. Thanks for your help!
This happens due to a bug in iOS7, where the bottom layout guide is incorrectly set to height 0 instead of the tab bar's height. When you rotate the device, the bottom layout guide is set correctly.
Currently, your best option is to disable bottom extended layout:
- (UIRectEdge)edgesForExtendedLayout
{
return [super edgesForExtendedLayout] ^ UIRectEdgeBottom;
}
Do this for each view controller that is displayed from the tab bar controller. Remember to set the tab bar view controller's background color to whatever suits your application.
Make sure to open a bug report at https://bugreport.apple.com
To elaborate a little more, it seems viewDidLayoutSubviews is called twice when switching view controllers. First time, everything is set correctly, but the second time bottom layout guide height is 0. You can see from the stack trace that the first one comes from tab bar layout, while the second call is from a scheduled CALayer layout, which is incorrect.
While Leo's answer shows how to do it programmatically, if you want to do this from the interface builder, select your View Controller and uncheck "Under bottom bars" from the Extend Edges section:
Calling setNeedsLayout is all that needs to be done. This essentially patches the framework bug. It needs to be called on the UITabBarController view itself when a new view is selected. Create a delegate for the app's tab bar controller. and put this in the delegate object:
#interface MyPatch : NSObject <UITabBarControllerDelegate>
#end
#implementation MyPatch
-(void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController
{
[tabBarController.view setNeedsLayout];
}
#end
And initialize it wherever you want... something like this:
#interface AppDelegate : UIResponder <UIApplicationDelegate>
{
MyPatch *patch;
}
#end
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
patch=[MyPatch new];
myTabBarController.delegate=patch;
}
#end
Leo is right, the bottomLayoutGuide is returned incorrectly.
But unsetting the extend edges under bottom bars (or overriding edgesForExtendedLayout) had too much undesired effects on other subviews for me.
If you want to change only the constraint for one view according to the bottom layout guide,
implement viewWillLayoutSubviews and check the value of the bottomLayoutGuide property and adapt that one constraint if required, like so:
- (void)viewWillLayoutSubviews {
[self adaptBottomLayoutGuides];
}
/// Workaround for iOS7 bug returning wrong bottomLayoutGuide length if this is 1st tab in TabViewController
- (void)adaptBottomLayoutGuides {
NSLog(#"%f", self.bottomLayoutGuide.length);
CGFloat expectedHeight = 123;
CGFloat adaptedSpacing = expectedHeight - self.bottomLayoutGuide.length;
self.viewBottomLayoutSpacingConstrain.constant = adaptedSpacing;
}

popover content view doesn't display while viewcontroller has a child VC present

I have a container view controller that consists of a navigation view at top, and a content view for the remainder of the screen. The navigation menu consists of several buttons, some of which present a popover with UITableView for secondary navigation. This all worked until I assigned a child view controller and set it's view as subview of the content view. Now, the popover appears, but has nothing inside it (no tableview, just black).
Why is this?
Here's the code I added for the child vc in container view:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
ContentWebViewController *initialVC = [[ContentWebViewController alloc] init];
[self addChildViewController:initialVC];
initialVC.view.frame = self.view.bounds;
[self.containerView addSubview:initialVC.view];
self.currentController = initial;
}
See the screenshot below. I added a vc with a simple webview showing google (just as a placeholder for now). The popover was working fine before I assigned the child VC.
Maybe it will help other in other cases -
If you are using size classes (probably you are since you are developing this to iPad) -
Design your popover view controller in Any-Any size and it should be OK - after that you can return to your wanted size.
(You can also uninstall the size classes of any object in that view controller instead of redesign the VC)
I somehow (don't ask me how) changed the class that my table view controller was inheriting from. It should have been (obviously) UITableViewController, but was UITableViewController, so initWithStyle was not being called....

Resources