Been struggling for about 3 days getting an implementation of a scrollable keyboard working. Here's what I have:
UIViewController with the following hierarchy:
The above diagram shows that I have a messaging style UITableView + "dockable" UIView with a textview and send button embedded in View->ScrollView->ContentView.
In viewWillAppear:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardFrameDidChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
And keyboardFrameWillChange implemented:
- (void)keyboardFrameWillChange:(NSNotification *)notification
{
CGRect endFrame = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
NSTimeInterval duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
UIViewAnimationOptions options = (curve << 16) | UIViewAnimationOptionBeginFromCurrentState;
CGRect newFrame = self.view.frame;
newFrame.origin.y = endFrame.origin.y - newFrame.size.height;
[UIView animateWithDuration:duration delay:0 options:options animations:^{
self.view.frame = newFrame;
} completion:nil];
}
Which gives me an almost completely functional show and hide of the keyboard just from using keyboardFrameWillChange. This includes setting Keyboard drag dismissal on the storyboard.
I'm having one issue though: the entire view (whatever view that is) is being shifted, so if there are only a few items in the tableview, my code scrolls the top part of the view out of the way so I can no longer see them.
I have tried resizing the tableview since what I believe is happening is the scrollview is scrolling the full sized tableview out of the way instead of the tableview being resized. I have a strong feeling I'm getting my views confused, but my efforts to fix this have been in vain.
FYI there are no autolayout issues and everything is attached to the proper views and such (i.e. UIView is docked below tableview and to the bottom of the parent scrollview.
How do I solve this?
A few suggestions that might help:
Don’t change both frame and contentInset; changing just one should be sufficient and easier to manage. I recommend only changing contentInset.
One of UIKeyboardWillChangeFrameNotification or UIKeyboardDidChangeFrameNotification should tell you everything you need; you can deal with the keyboard in one method. The frame will go offscreen when the keyboard hides.
When you undo your changes to contentInset, put this property back to how you found it. UIViewController will automatically adjust scroll view insets to avoid the status bar, navigation bar, and toolbar or tab bar. This might be the cause of your first problem.
I know libraries are junk, but you might want to check out my DHAvoidKeyboardBehaviour. At least it’s only 34 lines of junk.
Also: you are registering for UIKeyboardWillChangeFrameNotification but your method is called keyboardFrameDidChange:. Being sloppy about the distinction between will and did will bite you one day.
For anyone who is interested, I solved my own question.
In keyboardFrameDidChange, I was shifting the entire tableview up to accommodate the keyboard, which was causing rows to appear off screen. What I was missing was changing the contentInsets to "resize" the tableview to fill the remaining space on the screen.
The insets became top adjusted for keyboard height and bottom adjusted for the toolbar. toolBar is the view anchored to the bottom below the tableview.
In viewWillAppear:
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillBeShown:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardFrameWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
In viewWillDisappear:
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
Implementation of keyboardWillBeShown:
NSLog(#"Keyboard will be shown");
CGRect keyboardFrame = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIEdgeInsets contentInsets = UIEdgeInsetsMake(keyboardFrame.size.height, 0.0, toolBar.bounds.size.height, 0.0);
tableView.contentInset = contentInsets;
tableView.scrollIndicatorInsets = contentInsets;
Implementation of keyboardWillBeHidden:
NSLog(#"Keyboard will be hidden");
tableView.contentInset = UIEdgeInsetsZero;
tableView.scrollIndicatorInsets = UIEdgeInsetsZero;
Implementation of keyboardFrameWillChange:
NSLog(#"Keyboard frame will change");
NSTimeInterval duration = [[notification.userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
UIViewAnimationCurve curve = [[notification.userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] unsignedIntegerValue];
UIViewAnimationOptions options = (curve << 16) | UIViewAnimationOptionBeginFromCurrentState;
CGRect keyboardFrame = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect contentFrame = self.view.frame;
contentFrame.origin.y = keyboardFrame.origin.y - contentFrame.size.height;
[UIView animateWithDuration:duration delay:0 options:options animations:^{
self.view.frame = contentFrame;
} completion:nil];
Thanks to Douglas Hill for pointing me in the general direction.
Related
I have a problem about the iOS at UIScrollview in Objective-C.
I drag the UI auto layout about below photo.
When I click the textfield, the keyboard will show, and I need can scroll the view.
It is seem is ok, but when I click done button and hide the keyboard,
I click textfield again, the keyboard will show, but I can't scroll the view again.
And other problem is like upper photo,
When I scrolled on the tableview , the tableview is can't scroll,
it is scroll the outside scroll view, how can I set the touch on the tableview , it's scroll tableview .
My keyboard event code below and I had upload this page complete code and auto layout project in GitHub:
this problem link
Have anyone can give me some help? thank you very much.
- (void)viewDidLoad {
[super viewDidLoad];
[self registerForKeyboardNotifications];
self.tf.inputAccessoryView = self.keyboardToolbarVw;
}
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0.0);
self.sv.contentInset = contentInsets;
self.sv.scrollIndicatorInsets = contentInsets;
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
if (!CGRectContainsPoint(aRect, self.view.frame.origin) ) {
CGPoint scrollPoint = CGPointMake(0.0, self.view.frame.origin.y-kbSize.height);
[self.sv setContentOffset:scrollPoint animated:YES];
}
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification
{
UIEdgeInsets contentInsets = UIEdgeInsetsZero;
self.sv.contentInset = contentInsets;
self.sv.scrollIndicatorInsets = contentInsets;
}
- (IBAction)keyboardToolbarDoenBtnAction:(UIButton *)sender {
[self.tf resignFirstResponder];
}
After reviewing you code, it seems problem UIScrollView frames update in - (void)keyboardWasShown:(NSNotification*)aNotification
Instead of doing such manually work on frames, please try IQKeyboardManager
I’ll suggest to use IQKeyboardManager library in all our projects :) It’s a drop-in universal library which allows you to prevent issues of the keyboard sliding up and covering UITextField/UITextView without needing you to writing any code and much more…
I have a view for which I'm trying to animate a height change when the keyboard slides up (code shown below). The view on which I'm calling the frame change is animating perfectly; however, That view contains a subview, which in turn contains a textfield (which is causing the keyboard to pop up), and these subviews are just jumping to their new locations instead of animating. I put auto layout constraints on the subview to constrain it to the left, bottom and right of the superview as well as maintain a constant height.
Example video here: http://youtu.be/EmcZ-cXeTbM
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(slideViewForKeyboard:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(slideViewForKeyboard:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)slideViewForKeyboard:(NSNotification *)note {
NSDictionary* userInfo = [note userInfo];
// Get animation info from userInfo
NSTimeInterval animationDuration;
UIViewAnimationCurve animationCurve;
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
CGRect newFrame = CGRectMake(self.view.frame.origin.x,
self.view.frame.origin.y,
self.view.frame.size.width,
keyboardEndFrame.origin.y);
[UIView animateWithDuration:0 delay:0 options:(animationCurve << 16) animations:^{
[self.view setFrame:newFrame];
}completion:^(BOOL completed){}];
}
- (IBAction)endEditingOnTap:(UITapGestureRecognizer *)sender {
[self.view endEditing:YES];
}
#end
You should be using animationDuration as the duration of your animation block.
Aside from that, you have another problem. Right now, you set the frame of your view inside the animation block, so the change is animated. But the system lays out the subviews later, during the layout phase of the run loop, outside your animation block. This means the subviews won't be animated to their new frames.
You can fix that by sending layoutIfNeeded to your view inside the animation block:
[UIView animateWithDuration:animationDuration delay:0 options:(animationCurve << 16) animations:^{
self.view.frame = newFrame;
[self.view layoutIfNeeded];
} completion:^(BOOL completed){}];
But you'll probably run into another problem. You're setting the frame of your view directly, but you're using auto layout. At some point, auto layout will probably set the frame back based on your constraints. You need to modify the constraints on your view to control its new frame, instead of setting the frame directly.
Set animation duration.
[UIView animateWithDuration:0.3 delay:0 options:(animationCurve << 16) animations:^{
[self.view setFrame:newFrame];
}completion:^(BOOL completed){}];
You're passing in 0 to -[UIView animateWithDuration:delay:options:animations:completion]. This will not result in an animation because its going from frame 0 to frame n-1 (where n is the number of frames it would have generated) in 0 seconds, aka instantaneously. Try passing your animationDuration as the argument for duration that way it animates at the same speed as the keyboard
My UIWebView is shifted up when the keyboard appears, and when the keyboard dismisses, the webview does not come back to its previous position. I've checked out the webview's position before and after the keyboard appears, or dismisses. What a surprise, the webview's frame is not changed. But what I've seen before and after are quite different.
Before the keyboard appears:
When the keyboard appears, the webview is shifted up
When the keyboard dismisses, the webview is not shifted down
What I did so far is applying the following techniques I read from others:
1) observe the keyboard notification, and adjust properly after that
observe keyboard notification
2) Change the meta tag, also add "height=device-height" in the meta tag
meta tag
3) Change the webview's attributes:
_webView.contentMode = UIViewContentModeScaleAspectFit;
_webView.scalesPageToFit = YES;
_webView.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
However, all of these suggestion above do not work. Could you please help me?
Note: I use iOS7, XCODE 5
A UIWebView has a UIScrollView in it to manage content which is bigger than the screen. When you adjust the height of a UIScrollView it usually has an impact on it's scroll position. When you shrink the height with a keyboard it causes content to scroll up to keep the text field visible (by modifying the contentOffset), but when you expand the height it just shows more content at the bottom and doesn't change the contentOffset back to the original value.
There are slews of ways to come at this problem and everyone who creates an editable text deals with it at some point. I've used many over the years I'm sure there are probably better ones I haven't even seen.
The way I did it last was by modifying the contentInset of the UIScrollView and saving off the original contentOffset so I can re-populate it later.
Set up your notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:nil];
Do stuff on notifications
- (void)keyboardWillShow:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardRect = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGFloat keyboardHeight = UIInterfaceOrientationIsPortrait(self.interfaceOrientation)?keyboardRect.size.height:keyboardRect.size.width;
CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
CGFloat animationStyle = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] floatValue];
self.tableViewOffset = self.tableView.contentOffset
UIEdgeInsets contentInsets = self.tableView.contentInset;
contentInsets.bottom = keyboardHeight;
[UIView animateWithDuration:duration delay:0 options:animationStyle animations:^{
self.tableView.contentInset = contentInsets;
} completion:nil];
}
- (void)keyboardWillHide:(NSNotification *)notification {
NSDictionary *userInfo = [notification userInfo];
CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
CGFloat animationStyle = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] floatValue];
UIEdgeInsets contentInsets = self.tableView.contentInset;
contentInsets.bottom = 0;
[UIView animateWithDuration:duration delay:0 options:animationStyle animations:^{
self.tableView.contentInset = contentInsets;
self.tableView.contentOffset = self.tableViewOffset;
} completion:nil];
}
What I solved my problem is
1) observe when the keyboard dismisses:
- (void)observeKeyboard {
[NotificationCenter addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)removeObserveKeyboard
{
[NotificationCenter removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
2) When the keyboard is about to hide, we trigger a js to scroll it up.
- (void)keyboardWillHide:(NSNotification *)notification {
[self scrollBackToTop];
}
//- Fix bug: the UIWebView Content is scroll up when the keyboard appears
-(void)scrollBackToTop {
[_webView stringByEvaluatingJavaScriptFromString:#"window.scrollTo(0, 0);"];
}
Thanks to this link : link
I have a table view with static cells. One cell contains a UITextView and the heightForRowAtIndexPath: is calculated dynamically so that the cell is always tall enough to accomodate the text (that part took some work under iOS 7, actually, as it's no longer possible to simply ask the textView for its contentSize).
When I tap within the text view to start editing, the keyboard animates into place, the contentInsets on the tableView are automatically adjusted to account for this (ie, bottom inset of 216px for iPhone portrait orientation), the cursor / caret becomes visible, and then the table view scrolls to another location. It ends up looking like a bounce.
Here's a video of this in the simulator: https://www.dropbox.com/s/htdbb0t7985u6n4/textview-bounce.mov
Notice that for a second the caret is just above the keyboard. I've been logging the table view's contentOffset and I can see it scroll to a nice value and then suddenly "turn around" and scroll back.
Oddly, if I turn on slow animations in the simulator the problem disappears; the contentOffset reversal doesn't happen and things work as I expect (ie, iOS 6 behavior).
Here's the video with slow animations: https://www.dropbox.com/s/nhn7vspx86t4exb/textview-nobounce.mov
Implementation notes:
The text view is pink and has AutoLayout constraints that keep it pinned to the cell at distance 0 (except left side, which is 10pts)
I'm using boundingRectWithSize: to calculate the table view height, adjusting for lineFragmentPadding and any top/bottom insets. Seems to work.
I have set the textView to not be scrollable, but didn't notice anything different when scrollEnabled == YES
This is a table view controller and automaticallyAdjustsScrollViewInsets == YES
Try to adjust UITableView frame when keyboard appears. Call [self attachKeyboardHelper] in viewWillAppear and [self detachKeyboardHelper] in viewWillDisappear.
- (void)attachKeyboardHelper{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillAppear:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
- (void)detachKeyboardHelper{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)keyboardWillAppear:(NSNotification *)notification{
NSDictionary* userInfo = [notification userInfo];
NSTimeInterval animationDuration;
UIViewAnimationCurve animationCurve;
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
// Animate up or down
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:animationDuration];
CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame toView:nil];
if(self.view==self.tableView){
CGRect newTableFrame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y, self.tableView.frame.size.width, self.view.bounds.size.height-keyboardFrame.size.height);
self.tableView.frame = newTableFrame;
}else{
CGRect newTableFrame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y, self.tableView.frame.size.width, self.view.bounds.size.height-self.tableView.frame.origin.y-keyboardFrame.size.height);
self.tableView.frame = newTableFrame;
}
[UIView commitAnimations];
}
- (void)keyboardWillHide:(NSNotification *)notification{
NSDictionary* userInfo = [notification userInfo];
NSTimeInterval animationDuration;
UIViewAnimationCurve animationCurve;
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
CGRect newTableFrame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y, self.tableView.frame.size.width, self.view.superview.bounds.size.height-self.tableView.frame.origin.y);
self.tableView.frame = newTableFrame;
if(newTableFrame.size.height>self.tableView.contentSize.height-self.tableView.contentOffset.y){
float newOffset=MAX(self.tableView.contentSize.height-newTableFrame.size.height, 0);
[self.tableView setContentOffset:CGPointMake(0, newOffset) animated:YES];
}
}
I have an embedded tableview within a view. The top rows of this embedded table view are always visible, even with the keyboard. If you enter something into the lower cells I want them to scroll up (as usual) in order to see what you're entering into the textfields. However this doesn't happen and they remain hidden behind the keyboard. How can I change this?
Thx
Michael
Here is a link with a detailed explanation to achieve this. It explains the method on UIScrollView, which will also work on UITableView.
If you had used a UITableViewController instead of manually adding UITableView on a view, you would had got the behavior automatically.
You have to scroll to the position between the TextField and Tableview, check this answer.
You don't get that behavior for free, you need to scroll the content up/down yourself. A UITableView inherits from a UIScrollView, so scrolling is not the actual problem. To scroll, you can set its contentSize and contentOffset (even animated if you like).
The first thing is, you want to listen to these notifications:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
These will tell you whether the keyboard will show or hide. In each of these methods, change the contentOffset and contentSize of your UITableView accordingly:
- (void)keyboardWillShow:(NSNotification *)notification
{
// keyboard info
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardRect = [[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIViewAnimationOptions animationCurve = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
NSTimeInterval animationDuration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
// offsets
UIView *cell = myTextField.superView; // assuming your text field is a direct child of the UITableViewCell
CGFloat textFieldBottomY = self.view.frame.origin.y + myTableView.frame.origin.y - myTableView.contentOffset.y + cell.frame.origin.y + myTextField.origin.y + myTextField.frame.size.height;
CGFloat keyboardTopY = keyboardRect.origin.y;
// new content size: add size to be able to scroll
originalContentOffset = myTableView.contentOffset; // store the original content offset to set back when keyboard hides
originalContentSize = myTableView.contentSize; // store the original content size to set back when keyboard hides
CGSize newContentSize = myTableView.contentSize;
newContentSize.height += keyboardRect.size.height;
myTableView.contentSize = newContentSize;
// scroll to just beneath your text field
[UIView animateWithDuration:animationDuration delay:0 options:animationCurve animations:^{
myTableView.contentOffset = CGPointMake(0, textFieldBottomY - keyboardTopY);
} completion:NULL];
}
To reset everything:
- (void)keyboardWillHide:(NSNotification *)notification
{
// keyboard info
NSDictionary *userInfo = [notification userInfo];
UIViewAnimationOptions animationCurve = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
NSTimeInterval animationDuration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
// move content
[UIView animateWithDuration:animationDuration delay:0 options:animationCurve animations:^{
myTableView.contentOffset = originalContentOffset;
} completion:^(BOOL finished) {
if (finished) {
// reset content size
myTableView.contentSize = originalContentSize;
}
}];
}
To detect which text field is selected/active:
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
myTextField = textField;
}
I didn't test out the code on a UITableView, but I used it very recently on a UIScrollView. Again - since a UITableView inherits from a UIScrollVIew, this should not be a problem. If the table view does not scroll correctly to just beneath the text field, the value of textFieldBottomY is incorrect.