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
Related
I have working code that pins UI to keyboard height when it appears:
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
self.bottomSpacing.constant = kbSize.height + 10;
[self.view layoutIfNeeded];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
self.bottomSpacing.constant = 10;
[self.view layoutIfNeeded];
}
But it has issue when device autorotates: keyboard height changes (ex. on iPad 313 => 398) and 'bottomSpacing' becomes outdated.
How to update it to new keyboard height? Alternatively, is it possible to assign autolayout constraint to keyboard view?
The simplest way is to listen to UIKeyboardWillChangeFrameNotification notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillResize:) name:UIKeyboardWillChangeFrameNotification object:nil];
...
- (void)keyboardWillResize:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
float keyboardTop = CGRectGetMinY([info[UIKeyboardFrameEndUserInfoKey] CGRectValue]);
float animationDuration = info[UIKeyboardAnimationDurationUserInfoKey] floatValue];
[self.view layoutIfNeeded];
[UIView animateWithDuration:animationDuration animations:^{
self.pinToKeyboardConstraint.constant = keyboardTop;
[self.view layoutIfNeeded];
}];
}
This notification fires whenever the keyboard changes bounds including show and hide events.
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.
I have tried some solutions that are out there, but they do not work they way I need them to.
I have quite a long text field that will have several paragraphs inside of it. When a user taps on the text field, their keyboard pops up and basically blocks about half the text that's already there and the keyboard prevents them from making any additions to the text field, since the user cannot see what they are typing.
I have tried modifying the frame definition to go above the keyboard, but since the textfield is so long, the user can still go below the keyboard if they add enough text. Wrapping the text view inside a scroll view doesn't do much either.
I am using swift + xcode 6
Here is a screenshot of what I am talking about:
Assuming you're using the autolayout, you can do the following :
In the .h, define a NSLayouConstraint :
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *keyboardHeight;
In the .m :
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillChangeFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
NSValue *kbFrame = [info objectForKey:UIKeyboardFrameEndUserInfoKey];
NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardFrame = [kbFrame CGRectValue];
self.keyboardHeight.constant = keyboardFrame.size.height;
[UIView animateWithDuration:animationDuration animations:^{
[self.view layoutIfNeeded];
}];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
NSDictionary *info = [notification userInfo];
NSTimeInterval animationDuration = [[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
self.keyboardHeight.constant = 20;
[UIView animateWithDuration:animationDuration animations:^{
[self.view layoutIfNeeded];
}];
}
Then link keyboardHeight with the vertical autolayout constraint between the UITextView and the bottom of the view :
Listen for the UIKeyboardWillShowNotification and UIKeyboardWillHideNotification to adjust the size of your text view as is appropriate. You can get the dimensions of the keyboard via the UIKeyboardFrameEndUserInfoKey of the user dictionary.
E.g. a Swift version of Apple's code:
func keyboardWasShown(aNotifcation: NSNotification) {
let info = aNotifcation.userInfo as NSDictionary?
let rectValue = info![UIKeyboardFrameBeginUserInfoKey] as NSValue
let kbSize = rectValue.CGRectValue().size
let contentInsets = UIEdgeInsetsMake(0, 0, kbSize.height, 0)
textView.contentInset = contentInsets
textView.scrollIndicatorInsets = contentInsets
// optionally scroll
let aRect = textView.superview!.frame
aRect.size.height -= kbSize.height
let targetRect = CGRectMake(0, 0, 10, 10) // get a relevant rect in the text view
if !aRect.contains(targetRect.origin) {
textView.scrollRectToVisible(targetRect, animated: true)
}
}
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.