How to test if keyboard is covering UITextField in UITableView? - ios

How can I check if the keyboard is covering a first responder inside a UIScrollView which may or may not be a UITableView? Note the UIScrollView will not necessarily cover the entire viewController's view and may be contained in a modal view (UIModalPresentationFormSheet).
I'm using this modified code from Apple's reference documentation and example, but CGRectContainsPoint will return false even when the keyboard is clearly covering the first responder. It's obvious I'm not using convertRect:toView correctly.
Also, Apple's code does not take into account that the view is not full-screen, so setting the scrollView's contentInset to the full height of the keyboard isn't a great solution -- it should only be inset for the portion of the keyboard covering the firstResponder.
- (void)keyboardWasShown:(NSNotification*)aNotification
{
// self.scrollView can be a tableView or not. need to handle both
UIView *firstResponderView = [self.scrollView findFirstResponder];
if (!firstResponderView)
return;
NSDictionary* info = [aNotification userInfo];
CGRect rect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
// convertRect:toView? convertRect:fromView? Always confusing
CGRect kbRect = [self.scrollView convertRect:rect toView:nil];
CGRect viewRect = [self.scrollView convertRect:firstResponderView.bounds toView:nil];
// doesn't work. convertRect misuse is certainly to blame
if (!CGRectContainsPoint(kbRect, firstResponderView.frame.origin))
return;
// Only inset to the portion which the keyboard covers?
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbRect.size.height, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
}

Without further testing or having a deep look at the logic, this line seems odd:
CGRect kbRect = [self.scrollView convertRect:rect toView:nil];
The keyboard rect (that is included in the notification) is in window coordinates and you probably want to convert it into the scroll view coordinate system. [viewA convertRect:rect toView:viewB] converts rect from viewA's coordinate system to viewB's coordinate system, so you are actually doing the opposite of what you should be doing (as you suspected).
What I'm usually doing is this:
- (void)keyboardWillShow:(NSNotification *)aNotification
{
NSDictionary *info = [aNotification userInfo];
CGRect kbRect = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
kbRect = [self.view.window convertRect:kbRect toView:self.view]; // convert to local coordinate system, otherwise it is in window coordinates and does not consider interface orientation
_keyboardSize = kbRect.size; // store for later use
[UIView animateWithDuration:0.25 animations:^{
UIEdgeInsets insets = UIEdgeInsetsMake(0.0f, 0.0f, MAX(0.0f, CGRectGetMaxY(_tableView.frame) - CGRectGetMinY(kbRect)), 0.0f); // NB: _tableView is a direct subview of self.view, thus _tableView.frame and kbRect are in the same coordinate system
_tableView.contentInset = insets;
_tableView.scrollIndicatorInsets = insets;
[self scrollToActiveTextField]; // here I adapt the content offset to ensure that the active text field is fully visible
}];
}

Related

Keyboard pushes UIScrollView content out of screen

I've been struggling with this keyboard and scrollview issue for quite sometime now. I'm trying to make a chat room similar to What'sApp and iMessage. I have UITabBar as a root view controller. For the chat room view I have a toolbar at the bottom that contains UITextView and UIButton the issue is that when the keyboard is presented it pushes the content view out of the screen and I can't see about 1/5 of the top of the content view. I tried playing with the numbers and still can't get it to work properly. Any help would be greatly appreciated.
- (void)keyboardWasShown:(NSNotification *) aNotification {
NSDictionary *info = [aNotification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
// the hardcoded 49 is the height of the UITabBar at the bottom below the input toolbar
UIEdgeInsets contentInsets = UIEdgeInsetsMake((-keyboardSize.height+49), 0.0, (keyboardSize.height-49), 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
// CGRect aaRect = self.view.frame;
// aaRect.size.height -= keyboardSize.height;
// if (!CGRectContainsPoint(aaRect, self.activeTextView.frame.origin)) {
// [self.scrollView scrollRectToVisible:self.activeTextView.frame animated:NO];
// }
CGPoint scrollPoint = CGPointMake(0, self.scrollView.contentInset.bottom);
[self.scrollView setContentOffset:scrollPoint animated:true];
[self.view addGestureRecognizer:self.tapRecognizer];
}
- (void)keyboardWillBeHidden:(NSNotification *) aNotification {
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
[self.view removeGestureRecognizer:self.tapRecognizer];
}
I met the same problem long time ago. My solution is to listen keyboard frame did change notification(because different keyboard has different frame). And I think it is easier to adjust the frame of scroll view rather than content offset.

UIScrollview loses its top contents when I move the containing view up with the keyboard ios

I have been pulling my hair out trying to fix this problem the last few weeks. I move up the entire view contained in my view controller when the keyboard appears. Once I do so the top portion of my scrollview contained in the view becomes unreachable. It is like the top of the screen is cutting off the top portion of my scrollview. Is there some way to fix this?
//Move the keyboard when you select a textfield.
-(void)keyboardWillShow:(NSNotification*)notification{
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
CGRect viewRect = self.view.frame;
viewRect.origin.y = -215;
[UIView animateWithDuration:0.3f animations:^ {
self.view.frame = viewRect;
}];
scrollBounces = YES;
scrollView.contentOffset = CGPointZero;
}
This is all that I am doing to the view that contains the scrollview.
You can set the contentInset and scrollIndicatorInsets for the scrollView too:
[scrollView setContentInset:UIEdgeInsetsMake(215, 0, 0, 0)];
[scrollView setScrollIndicatorInsets:UIEdgeInsetsMake(215, 0, 0, 0)];
Write the below three lines in the keyboardWillShow
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kKeyboardHeightValue, 0.0);
self.mDisplayedScrollview_.contentInset = contentInsets;
self.mDisplayedScrollview_.scrollIndicatorInsets = contentInsets;
Write the below three lines in the keyboardWillHide
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
self.mDisplayedScrollview_.contentInset = contentInsets;
self.mDisplayedScrollview_.scrollIndicatorInsets = contentInsets;

iOS: scrollView not scrolling to correct position when keyboard visible

I am testing my iOS app using a 6.1 simulator. I have been working for hours to scroll my scrollView to the correct position when a keyboard is visible (after a user clicks on a textView). I have tried following the answer marked as correct on this page:
How do I scroll the UIScrollView when the keyboard appears?
This is what I currently have:
- (void)keyboardWasShown:(NSNotification*)aNotification {
NSLog(#"keyboardWasShown");
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
self.scrollView.contentInset = contentInsets;
self.scrollView.scrollIndicatorInsets = contentInsets;
// If active text field is hidden by keyboard, scroll it so it's visible
// Your app might not need or want this behavior.
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, self.activeView.frame.origin) ) {
NSLog(#"scrollToView");
CGPoint scrollPoint = CGPointMake(0.0, self.stepDescriptionField.frame.origin.y-kbSize.height);
NSLog(#"scrollPoint: %f", scrollPoint.y);
[self.scrollView setContentOffset:scrollPoint animated:YES];
}
}
You can see from the images above that if the user clicks on the textView, the scrollView is not scrolled to the correct position (you should be able to see the text contents of the textView).
The strange thing is that I manually tried changing the y-offset of the scrollPoint to different values, but it seemed to have no effect on where the window scrolls to. What am I doing wrong?
Other things that may be important:
I have autolayout turned off (so that the user can scroll vertically in this view).
the textView is not scrollable (it is resized to fit its contents)
Edit
I found that if I add my offset to the contentInsets as follows:
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height+50.0, 0.0);
the view will scroll to the correct position. The only downside is that there is extra padding at the bottom:
Is there a better way to do this?
I used this with a UITextField and not a UITextView but i believe it should still work the same. This allows me to positions the textfield directly above the keyboard.
keyboardWillShow is the function when NSNotificationCenter receives UIKeyboardWillShowNotification
-(void) keyboardWillShow:(NSNotification *)note
{
// Get the keyboard size
CGRect keyboardBounds;
[[note.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] getValue: &keyboardBounds];
// Start animation
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.3f];
// Get Keyboard height and subtract the screen height by the origin of the textbox and height of text box to position textbox right above keyboard
self.scrollView.contentOffset = CGPointMake(0,keyboardBounds.size.height-([UIScreen mainScreen].bounds.size.height - commentBox.frame.origin.y - commentBox.frame.size.height));
[UIView commitAnimations];
}

contentInset being ignored in ios7 for UIScrollView

This worked before ios7 when someone tapped on anything that could become first responder inside a UIScrollView. Now it does not - UITextFields/Views still can show under the keyboard.
Code:
- (void)keyboardWasShown:(NSNotification*)notification{
//Some similar questions mentioned this might work, but made no difference for me
self.automaticallyAdjustsScrollViewInsets=NO;
NSDictionary* info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
float height = 0.0;
if (UIDeviceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
height = kbSize.width;
} else {
height = kbSize.height;
}
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, height, 0.0);
[UIView animateWithDuration:.25
delay:0
options:(UIViewAnimationOptionAllowUserInteraction)
animations:^
{
self.editorScrollView.contentInset = contentInsets;
self.editorScrollView.scrollIndicatorInsets = contentInsets;
}
completion:^(BOOL finished)
{
}];
}
Currently, with this code nothing takes place when a uitextfield/view is assigned first responder status. The insets don't seem to change - I perhaps could use contentOffset but I would have to find the origin view's Y who just become first responder to do that.
Like I said, before ios7 this code worked (no textfield/view would be hidden behind the keyboard when assigned first responder status). I seem to be missing something obvious or perhaps there is a better way of doing this in ios7?
A better way to detect keyboard changing and frame.
The key point is to convert keyboard frame: CGRect keyboardFrameInsideView = [self.view convertRect:keyboardFrame fromView:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardFrameWillChange:)
name:UIKeyboardWillShowNotification
object:nil];
- (void)keyboardFrameWillChange:(NSNotification *)notification
{
CGRect keyboardFrame;
[[notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardFrame];
CGRect keyboardFrameInsideView = [self.view convertRect:keyboardFrame fromView:nil];
CGRect r = self.bodyView.frame;
r.size.height = CGRectGetMinY(keyboardFrameInsideView) - r.origin.y;
self.bodyView.frame = r;
}

Table view not adjusting to keyboard properly

I have a custom inherited UIView class with a UITableView within it as its only subview. I'm trying to mimic the normal functionality of the UITableViewController when the keyboard is shown to adjust the contentInset and scrollIndicatorInsets of the table view to the height of the keyboard. This is my method that gets called when the keyboard did show from within my custom UIView class:
- (void)keyboardDidShow:(NSNotification*)notification
{
NSDictionary* info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
_tableView.contentInset = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
_tableView.scrollIndicatorInsets = _tableView.contentInset;
}
This works to a certain extent, but there is still some overlap of the keyboard onto the table view for some reason by maybe ten or so pixels.
I'm thinking it has something to do with not taking into account some of the other screen geometry but I don't see how that could be. The height of the keyboard should be exactly what I need because the tableView stretches all the way to the bottom of the screen. Any ideas?
Change the tableView.frame.size.height, to account for the keyboard.
when keyboard is showing, reduce the height,
when not showing, increase the height.
refer to this if you want to consider the keyboard height for all possibilities http://www.idev101.com/code/User_Interface/sizes.html
Dont mess with the contentInset and the scrollIndicatorInsets. Just setting the frameSize will take care of these for you.
this is how your method should be
- (void)keyboardDidShow:(NSNotification*)notification
{
NSDictionary* info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGRect rect = _tableView.frame;
rect.size.height = _tableView.frame.size.height - kbSize.height;
_tableView.frame = rect;
}
- (void)keyboardWillHide:(NSNotification*)notification
{
NSDictionary* info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGRect rect = _tableView.frame;
rect.size.height = _tableView.frame.size.height + kbSize.height;
_tableView.frame = rect;
}
I have used this piece of code for a similar functionality. So if its still not working, there's something else going wrong.
I am curious why this isn't working for you, as I have basically the same thing and it is working for me. There is only one difference that I can see, in that I don't access '_tableView' and instead make sure that I'm always using the getter and setter.
Here is what I do, that is working.
- (void)keyboardDidShow:(NSNotification *)keyboardNotification
{
NSDictionary *info = [keyboardNotification userInfo];
CGSize keyboardSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGFloat newBottomInset = 0.0;
UIEdgeInsets contentInsets;
if (UIDeviceOrientationIsPortrait([[UIApplication sharedApplication] statusBarOrientation]) ) {
newBottomInset = keyboardSize.height;
} else {
newBottomInset = keyboardSize.width;
}
contentInsets = UIEdgeInsetsMake(0.0, 0.0, newBottomInset, 0.0);
self.tableView.contentInset = contentInsets;
self.tableView.scrollIndicatorInsets = contentInsets;
}
Note that my app allows device rotation and when that happens the value used needs to be the width of the keyboard because the values are relative to the portrait orientation, which caused me hours of confusion.
Hopefully the self.tableView access will make the difference.

Resources