Rotate iPad while popover is visible and my UITableView moves - ios

This is REALLY weird and I'm not sure what the heck is going on.
I've got a UISplitViewController on my iPad app. I can rotate from portrait to landscape over and over and it works exactly as expected. However, if I'm in Portrait orientation and I tap the button to show the popover which contains the left-hand side of the UISplitViewController and then rotate while it is visible, when the rotation is complete the left-hand side table is now pushed down leaving a black gap between it and the Nav Bar.
What the heck could cause that?
I have commented out my -willRotateToInterfaceOrientation:duration and -didRotateFromInterfaceOrientation: methods and the problem still exists, so it's not being caused by my rotation code. (That code was just explicitly dismissing that popover).
After the rotation which causes the problem, I recursively printed out the view hierarchy of the left side along with frame sizes:
UILayoutContainerView ({{0, 0}, {320, 748}})
UINavigationBar ({{0, 0}, {320, 44}})
UILayoutContainerView ({{0, 37}, {320, 711}})
UINavigationTransitionView ({{0, 0}, {320, 711}})
UIViewControllerWrapperView ({{0, 44}, {320, 667}})
UIView ({{0, 0}, {320, 667}})
UITableView ({{0, 0}, {320, 667}})
It shows that the navbar is 44 pixels tall, the view container which holds my left-side view controller starts at 0,44 and my tableview within starts at 0,0, but is shorter.... I don't understand why it's not displaying correctly.
Here is what is should look like in landscape orientation. On the left, you'll see the table section header "What do you want to know?" and it is right below the Nav Bar with the "Switch to Calculator" button.
When I swap to Portrait orientation and tap the "What do you want to know?" button, the popover shows which contains the left-hand side navigation controller with the UITableViewController.
Now, when I rotate back to landscape, the black gap appears between the nav bar and the UITableView.

I remembered having a problem very similar to this last year while working on this app, but I wasn't able to find any old StackOverflow posts about it. However, I remembered that there was some reason why I included the navbar (or rather, made it visible) inside the popover when in portrait mode. For aesthetic purposes, I'd rather have it visible in landscape, but not visible in the portrait popover.. and then I remembered the reason I included it was because it seemed to fix this very problem.
It appeared to be a weird bug in iOS, but showing the Nav Bar is both situations seemed to fix it. So, I decided to change it back to the way I really wanted it to be: visible in landscape, not visible in portrait... and BAM. The black gap disappears. So, it appears that weird bug is still there, but it is working in reverse now.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
UIDeviceOrientation deviceOrientation = [UIApplication sharedApplication].statusBarOrientation;
BOOL isPortrait = UIDeviceOrientationIsPortrait(deviceOrientation);
if (isPortrait) {
self.navigationController.navigationBarHidden = YES;
} else {
self.navigationController.navigationBarHidden = NO;
}
}
}

Related

Root ViewController View is not taking TabBar and NavBar into account

I have a viewcontroller that is embedded in both a navigation and tabbar viewcontroller. I had assumed that the self.view would geometrically be the bounds starting at the bottom of the navbar and the top of the tab bar. This is what I have read online. That by viewWillAppear the view is resized to subtract the tab and nav bar from the height, essentially. However, this is not happening for me. When I log out the frame of my view it is the same... {{0, 0}, {375, 667}}. In viewDidLoad, viewDidLayoutSubview, willAppear, DidAppear it is that same frame. Therefore, if I put an imageView as a subview of self.view with a frame (0,0,screenWidth, 200). That imageView appears occluded by the nav bar. To my reading, this isn't the standard behavior.
If you want that your UIViewController starts below the navigation bar put this on viewDidLoad:
self.edgesForExtendedLayout = UIRectEdgeNone;

UIKeyboard adopts an invalid frame

I currently have an UICollectionView, and a UITextField on the bottom of the screen. The textField moves according to the frame of the keyboard (very much like an inputAccessoryView, but I didn't use it for other reasons), and the collection view gets its insets modified when the keyboard appears, very standard stuff.
Anyway, there's a button in the screen, that on press, shows a modal UIViewController that covers the entire screen, and I dismiss the keyboard if my textField isFirstResponder.
Now, when this modal view finishes its business, it gets dismissed, we are back at the collection view and textfield, but now, the collection view has infinite insets, and the keyboard doesn't show again.
I check the notification I get for UIKeyboardWillChangeFrameNotification, and this is the userInfo:
Printing description of notification:
NSConcreteNotification 0x1ee4e900 {name = UIKeyboardWillChangeFrameNotification; userInfo =
{
UIKeyboardAnimationCurveUserInfoKey = 0;
UIKeyboardAnimationDurationUserInfoKey = "0.4";
UIKeyboardBoundsUserInfoKey = "NSRect: {{0, 0}, {320, 0}}";
UIKeyboardCenterBeginUserInfoKey = "NSPoint: {160, 1136}";
UIKeyboardCenterEndUserInfoKey = "NSPoint: {160, 568}";
UIKeyboardFrameBeginUserInfoKey = "NSRect: {{inf, inf}, {0, 0}}";
UIKeyboardFrameChangedByUserInteraction = 0;
UIKeyboardFrameEndUserInfoKey = "NSRect: {{inf, inf}, {0, 0}}";
}}
The keyboard is coming up as {{inf, inf}, {0, 0}}. I already wrote a check (with isInf) to prevent this, but now the keyboard gets permanently screwed up, and it won't show up anywhere in the app anymore, so this has become a serious issue. This happens on iOS6 and iOS7, on simulator and device.
I'm going through all the code to see if there's anything weird that is causing the issue, but I'm hoping some has ran into something similar, and has some insight on what is causing this.
After 3 months, I finally figured it out (I've been doing other stuff, FYI).
The short answer
Make sure you're not returning crap from any rotation method, otherwise keyboard goes loco.
The long version
Discovered by using the application Reveal, that draws a 3d decomposition of each layer in the app. I noticed the keyboard was being presented but it's frame was landscape, when the interface was in portrait. This immediately made me revise the whole way the rotation is being handled.
The whole problem spans from a UIViewController that posses child view controllers with a complex structure, and the rotation calls are getting forwarded to the children manually. For example, this method:
- (NSUInteger)supportedInterfaceOrientations
{
return [[[(id)self.selectedViewController viewControllers] lastObject] supportedInterfaceOrientations];
}
Was relying on whatever top-most view controller on the current selected navigation controller to return the value. Well, one of the view controllers went rogue, it wasn't returning anything here, so the return value must've been just about anything in memory or zero.
The solution now checks if the view controllers responds to the method, and if not, it defaults to portrait (which is about 90% of the app).
- (NSUInteger)supportedInterfaceOrientations
{
NSUInteger supported = UIInterfaceOrientationMaskPortrait;
UIViewController *viewController = [[(id)self.selectedViewController viewControllers] lastObject];
if (viewController && [viewController respondsToSelector:#selector(supportedInterfaceOrientations)])
supported = [viewController supportedInterfaceOrientations];
return supported;
}
Btw, the sane way to do it would be to just return YES from -(BOOL)shouldAutomaticallyForwardRotationMethods (iOS 6, 7) but this was an impossibility due to how it was implemented. For now, it's on NO and being manually handled.
There's still the question why it only mattered with presented view controllers. Although, I rather just discard it as "random behavior from returning an invalid interface orientation".

iOS MapView goes under navigation bar, status bar, and tab bar controller

I have a ViewController with a navigation controller and a tab bar controller. This ViewController has 2 buttons that toggle the visibility of a scroll view and a map view. The main thing is to have these 2 buttons always show up in the same place regardless of orientation or the view that happens to be visible.
The problem I am having is that the MapView won't size properly. If I just give it a frame from self.view.bounds it goes under the navigation bar / tab bar - basically taking up the whole screen. This throws off the location of my toggle buttons.
I noticed my ScrollView does the same (using a background color and a translucent navigation bar) but the positioning of sub views on it stay within the visible area (between the navigation bar and the tab bar). So when I add my toggle buttons, they show in the correct place.
When I press the toggle buttons, I just re-assign the parent view of the buttons to the now displayed view (ScrollView or MapView). This always works on the scroll view but due to the positioning, they end up going under the navigation bar when the MapView is displayed.
I have tried creating the frame for the MapView manually but I get odd results. I use this for the frame:
CGRect mapFrame = CGRectMake(
0,
(self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height),
self.view.frame.size.width,
(
self.view.frame.size.height
- (self.navigationController.navigationBar.frame.size.height + [UIApplication sharedApplication].statusBarFrame.size.height + self.tabBarController.tabBar.frame.size.height)
)
);
I then set the auto resizing masks
[self.mapView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin];
But with this, if I enter into the view controller in landscape mode - the MapView is off the screen. If I enter in portrait mode and then rotate to landscape, the top margin is off but about 10 points so it still goes under the navigation bar a bit (and has a visible margin between the tab bar and the bottom of the map view).
How can I make the MapView subviews only display within the visible region like the ScrollView does? I don't mind so much if the map itself goes under the navigation bar / tab bar
Ugh, I found the answer just a few minutes ago. I have no idea why this is the default behavior on iOS 7 but alas, there it is.
The solution is to add this to the viewDidLoad on the ViewController
[self setEdgesForExtendedLayout:UIRectEdgeNone];
[self setAutomaticallyAdjustsScrollViewInsets:NO];
Much thanks to the post here

Lost Gestures iPad landscape

I have an app with an MGSplitView containing a table view and a UIWebView, which is fixed to landscape. The web view has a UITapGestureRecognizer (for a triple tap) attached to the web view. Taps in the left portion of the web view work; taps on the right side of the web view are lost - the action is not triggered, and the gesture delegate messages are not received.
The problem seems not to lie in the MGSplitViewController, as switching to a UISplitViewController has the same issues; changing from a tap to a long press also has the same results.
Tap locations are reported with the x coordinate at or close to the max width of the gesture.view, and yet are clearly made close to the centre of the display, which I expect has something to do with the root of the problem - and yet the web view contents are clearly visible and correctly placed.
All the view controllers involved implement shouldAutorotate and supportedInterfaceOrientations, so being stuck in portrait seems unlikely i.e. MGSplitViewController, my UITableView subclass (left hand panel) and UIViewController subclass for the right hand panel.
My gesture recognizer delegate and output from one triple-tap (the view in the right hand panel web view):
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
NSLog(#"%s", __PRETTY_FUNCTION__);
NSLog(#"gestureRecognizer view frame: %#", NSStringFromCGRect(gestureRecognizer.view.frame));
NSLog(#"location %#", NSStringFromCGPoint([gestureRecognizer locationInView:gestureRecognizer.view]));
return YES;
}
-[DocumentBrowser gestureRecognizer:shouldReceiveTouch:]
gestureRecognizer view frame: {{0, 0}, {703, 704}}
location {703, -20}
-[DocumentBrowser gestureRecognizer:shouldReceiveTouch:]
gestureRecognizer view frame: {{0, 0}, {703, 704}}
location {414.5, 204.5}
-[DocumentBrowser gestureRecognizer:shouldReceiveTouch:]
gestureRecognizer view frame: {{0, 0}, {703, 704}}
location {414.5, 204.5}
The first location reported seems strange.
Check your orientation and view resizing mechanisms. I've seen this several times when something is wrong in these areas - if you log the touch locations, I think you will probably find that they stop at 768 points from the left-hand side, i.e. there is a view somewhere that thinks it is in portrait orientation.

iOS Truncated Views while rotating, have to reset view.bounds

I've been adding support for rotation for an app recently and it has been a pain. One thing I'm finding that's fairly consistently annoying is that one of my views shifts up by about 50 pixels or so everytime I rotate between my landscape and portrait mode.
My landscape mode is not actually the same view controller; I push a viewcontroller when I rotate. However, when I rotate back, I have to reset the portrait's view.bounds or else my view ends up shifting upwards.
So in my rotation code, I have to do this:
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
UIInterfaceOrientation toOrientation = self.interfaceOrientation;
if ( self.tabBarController.view.subviews.count >= 2 )
{
if(toOrientation == UIInterfaceOrientationPortrait)
{
self.tabBarController.tabBar.hidden = NO;
self.navigationController.navigationBar.hidden = NO;
CGFloat height = [[UIScreen mainScreen] bounds].size.height;
CGFloat width = [[UIScreen mainScreen] bounds].size.width;
self.view.bounds = CGRectMake(0, -55, width, height);
}
}
}
Surely this can't be right. In my app, there is a navbar and the standard status bar (batt life, reception, etc) occupying the top of my app. But...it seems like my view is slipping too upwards unless I set the y coordinate origin to be negative (which makes no sense!).
What's happening?
In my app, I hide the tabbar and navbar when I go to landscape mode. The statements to make the bars hidden are written into the portrait view's viewcontroller's code.
When I transition back from landscape mode to portrait mode, the landscape viewcontroller gets popped and I get the weird shifted views. Turns out this was caused by the order in which the tab/nav bar un-hiding statements.
My tab/nav bar un-hiding statements were in the portrait viewcontroller, so they were called too late. After moving the tab/nav un-hiding statements to the rotation code in the landscape viewcontroller (rather than the portrait's viewcontroller), my problem disappeared.

Resources