UIKeyboard adopts an invalid frame - ios

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".

Related

iOS Sizing UITableView During Orientation Change

I have a view that has two tables. In the story board, I have two separate views, one horizontal and the other vertical. When I need to navigate to the view, the code detects the orientation and brings up the appropriate view (and does so on an orientation change.
I have the following code in my method:
-(void)viewDidAppear:(BOOL)animated{
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if(orientation == UIInterfaceOrientationLandscapeLeft || orientation == UIInterfaceOrientationLandscapeRight){
if(tableHeight2 > 324){
tableHeight2 =325;
}
table1.frame = CGRectMake(table1.frame.origin.x, table1.frame.origin.y, table1.frame.size.width, tableHeight1);
table2.frame = CGRectMake(table2.frame.origin.x, table1.frame.origin.y + 20 + tableHeight1, table2.frame.size.width, tableHeight2);
}else {
if(tableHeight2 > 500){
tableHeight2 = 500;
}
table1.frame = CGRectMake(table1.frame.origin.x, table1.frame.origin.y, table1.frame.size.width, tableHeight1);
table2.frame = CGRectMake(table2.frame.origin.x, table1.frame.origin.y + 50 + tableHeight1, table2.frame.size.width, tableHeight2);
}
}
This works wonderfully when I press a button to navigate to the view. It adds up all of the cell heights and makes the first table the appropriate height, then moves the second table 50 pixels below the first table. It also makes sure the second table doesn't extend beyond the visible screen area.
When the orientation changes, I the following code is executed:
-(void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation duration:(NSTimeInterval)duration {
InitViewController *ini;
ini = [self.storyboard instantiateViewControllerWithIdentifier:#"Init"];
ini.location = MenuName;
[self presentViewController:ini animated:NO completion:nil];
}
This should do the same thing that pressing a barbuttonitem does: change to InitViewController while sending the StoryboardID to it in the ini.location variable. The code for the navigation buttons is pretty much identical to the code in willAnimateRotationToInterfaceOrientation. InitViewController then determines the orientation and sends the app to the correct storyboard UIView.
It does send it to the right view, I can tell based on the table widths. What it doesn't do is change the height of the first (top) table, table1. The first table retains the size it was given in the storyboard.
If there is area of code you think I need to post to get a better picture, let me know I'll be happy to add it. Any help, insight, or even just trial-and-error suggestions would be appreciated.
*Note: I have tried to change willAnimateRotationToInterfaceOrientation to ViewDidLayoutSubviews, to not effect.
Well, it seems a very small change fixed it. I noticed that the code on the navigation buttons had YES under "animate" for the view change, and the willAnimateRotationToInterfaceOrientation "animated:NO". I changed it to "YES" and that fixed it. Not sure why yet, perhaps it affects how the method displays the view or affects the load order, but there it is.

Rotate iPad while popover is visible and my UITableView moves

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;
}
}
}

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.

Animate UITextview change out, Keep keyboard in place

I have a UITextview, becomes first responder and keyboard is presented. Presently, I have buttons in inputAccessoryView toolbar that exchange the text forward and reverse through an array of strings.
I have the process working with no issues, so of course I am inclined to break it. My wish is to slide the textview left and right like a carousel to make it more clear to the user that next or previous string is coming and going. The current system simply replaces the text with no animation.
My first thought was to create a UINavigationController, give it an array of UIViewControllers that present the UITextviews. The navigation controllers view is only as big as the textview and I add it as a subview to my full view (which is itself in a navigation controller). I got this working fairly completely, the navigation bar is hidden and it looks no different than the original textview except that the textview now slides off to the right or left, depending if I am pushing or popping.
The problem with that is that the keyboard slides off along with the dismissed view controller, then the new textview in the new controller becomes first responder and the keyboard returns. Close, but no cigar.
I considered using page view controller but it seems it will have the same issue. I think I may have to go back to the single textview and animate the whole process directly with static screen grabs. That is completely beyond my experience level and I am think there must be a simpler way.
Can anyone suggest a simple way to keep that keyboard present while the views are swapped as described? Suggestions on other angles of attacking this?
Seems like an awful lot of overhead for a simple animation.
Try something like this (assuming ARC):
typedef enum _eDirection
{
rightToLeft = -1,
leftToRight = 1
} eDirection;
- (void) animateTextFields:(eDirection)direction
{
CGFloat distance = self.window.bounds.size.width;
UITextField *oldTextField = thisView.textField;
CGRect oldTFRect = oldTextField.frame;
CGRect newTFRect = CGRectOffset(oldTFRect, -direction * distance, 0);
UITextField *newTextField = [[UITextField alloc] initWithFrame:newTFRect];
[newTextField setText:#"whatever"];
[self addSubview:newTextField];
[UIView animateWithDuration:0.3f
animations:^{
[oldTextField setFrame:CGRectOffset(oldTextField.frame, direction * distance, 0)];
[newTextField setFrame:CGRectOffset(newTextField.frame, direction * distance, 0)];
} completion:^(BOOL finished) {
[self setTextField:newTextField];
[self removeSubview:oldTextField];
[newTextField becomeFirstResponder];
}];
}
(Disclaimer: as with all code typed off the top of one's head, etc., etc.)
This method would create a new textField, animate it horizontally onto the screen while moving the existing one off the screen in the direction you give, and then assigns the new textField as the current one and makes it the firstResponder.

Cannot resize AdMob's view upon orientation change

What I tried so far is, in viewDidLoad, I called
self.bannerView.autoresizingMask=UIViewAutoresizingFlexibleWidth;
and
- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)newInterfaceOrientation duration:(NSTimeInterval)duration {
if (newInterfaceOrientation == UIInterfaceOrientationLandscapeLeft || newInterfaceOrientation == UIInterfaceOrientationLandscapeRight) {
self.bannerView.frame=CGRectMake(0.0,
0.0,
480.0,
GAD_SIZE_320x50.height);
}
// Position the UI elements for portrait mode
else {
self.bannerView.frame=CGRectMake(0.0,
0.0,
GAD_SIZE_320x50.width,
GAD_SIZE_320x50.height);
}
}
Both of these didn't work for me.
Hmm, I don't think that AdMob's creatives can stretch to fit the size of the screen when in landscape. So despite the fact that you're stretching the frame of the view to fit, the ad itself I think will stay the same size.
This means you should still see an ad come in on orientation changes, it will just look like it's the same size (make sure to make another request for an ad in the willAnimateRotationToInterfaceOrientation: method to see this).
You don't need to do any moves, but you must set correct rootViewController for adMovView.
If you use view controller model please add line in each custom view controller
adMobView.rootViewController = viewController;
where viewController - root view controller of your app.
Do not code like this
adMobView.rootViewController = self;
in custom view!

Resources