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
Related
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 a UIView which contains another UIView object called contentView. Inside contentView, I have several UITextField objects. So that the current editing UITextField is always visible and not being hidden by the keyboard, I am altering the constraints on the contentView inside the textFieldDidBeginEditing: method. This means the contentView slides up and down inside the parent UIView, and keeps the relevant UITextField visible. This part is working fine, but here is a code fragment:
- (void)textFieldDidBeginEditing:(UITextField *)textField
//offset calculation removed for clarity
NSInteger offset = ....
self.contentViewTopConstraint.constant = -offset;
self.contentViewBottomConstraint.constant = offset;
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
I have noticed that if I type some text into the first UITextField, and then tap on the second UITextField, the text in the first UITextField jumps upwards and then back down again. If I disable the animation in the above code fragment, this behaviour goes away. So my guess is that when editing a UITextField, some other constraints are set, which are then altered as focus moves away from that UITextField. As I'm telling the view to update it's constraints in an animated fashion, this causes the text to move around.
Is there some way I can avoid this, but still maintain the animation for moving the contentView up and down?
Edit: Adding an extra [self.view layoutIfNeeded] call before I update any constraints fixed the issue. I'm still not to sure what might have been going on though, or really why this fixed it. Anyone have any insight?
I have since learned that the correct way to animate constraints is like this:
- (void)textFieldDidBeginEditing:(UITextField *)textField
//offset calculation removed for clarity
NSInteger offset = ....
[self.view layoutIfNeeded];
self.contentViewTopConstraint.constant = -offset;
self.contentViewBottomConstraint.constant = offset;
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}
This resolved the issue I was having.
I am not sure what is wrong with your piece of code, more code would be required for me to tell what is wrong in that, But you can use this piece of code, it will work for you
-(void)addObservers
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardIsShowing:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardIsHiding:)
name:UIKeyboardWillHideNotification
object:nil];
}
-(void)removeObservers
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardIsShowing:(NSNotification*)notification
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]];
[UIView setAnimationBeginsFromCurrentState:YES];
int scaleFactor = 0;
if([oldPasswrdField isFirstResponder])
scaleFactor = 20;
else if([newPasswrdField isFirstResponder])
scaleFactor = 76;
else if([confirmPasswrdField isFirstResponder])
scaleFactor = 132;
self.contentView.frame = CGRectMake(self.contentView.frame.origin.x, self.view.frame.origin.y-scaleFactor,self.contentView.frame.size.width, self.contentView.frame.size.height);
[UIView commitAnimations];
}
- (void)keyboardIsHiding:(NSNotification*)notification
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]];
[UIView setAnimationBeginsFromCurrentState:YES];
self.contentView.frame = CGRectMake(self.contentView.frame.origin.x, 0, self.contentView.frame.size.width, self.contentView.frame.size.height);
//self.view.frame = frame;
[UIView commitAnimations];
}
You see the scalefactor in keyboardIsShowing method. It is needed to be set according to the textfield you are editing. If you can, you can just fetch the current responding text field and take it's origin.y and set the scale factor according to that. Also, call addObservers in viewDidAppear and call removeObservers in viewDidDisappear. Or according to your needs, but don't forget to call them both once.
Edit: Tell me if this works for you or if you need further help on the matter, would be glad to assist.
I have a TextView. When keyboard appears, part of the TextView is hidden behind keyboard. So, I decrease its height by decreasing its bottom constraint(with its superview's bottom) so that user can scroll and see whole text.
But issue is when keyboard disappears its not resizing TextView's height to original. But when I tap on it, it shows whole text same as in first image.
How it looks normally :
How it looks when keyboard has appeared :
How it looks when keyboard has disappeared :
Now, if I tap on TextView it looks same as first image. i.e. resizes to have proper size :
Code : self.messageView is TextView.
// Called when keyboard is going to be displayed
- (void)keyboardWillShow:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
// get animation info from userInfo
NSTimeInterval animationDuration;
CGRect keyboardFrame;
[[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardFrame];
// Save constant to set back later
self.bottomConstant = self.noticeMessageBottom.constant;
// Change bottom space constraint's constant
self.noticeMessageBottom.constant = keyboardFrame.size.height + 8.0f;
[UIView animateWithDuration:animationDuration delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.messageView setNeedsLayout];
[self.messageView layoutIfNeeded];
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
} completion:nil];
[self.messageView flashScrollIndicators];
}
// Called when keyboard is going to be hidden
- (void)keyboardWillHide:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
// get animation info from userInfo
NSTimeInterval animationDuration;
CGRect keyboardFrame;
[[info objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[info objectForKey:UIKeyboardFrameBeginUserInfoKey] getValue:&keyboardFrame];
// Reset contentOffset
self.messageView.contentOffset = CGPointZero;
// Change bottom space constraint's constant
self.noticeMessageBottom.constant = self.bottomConstant;
[UIView animateWithDuration:animationDuration delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
[self.messageView setNeedsLayout];
[self.messageView layoutIfNeeded];
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
} completion:nil];
}
Update : I set background color to TextView and saw that height is changed properly but part of the text is not visible. It becomes visible when I tap on it.
Note : This happens only if TextView's contentOffset is (0,0).i.e user has not scrolled it. If user has scrolled it and contentOffset is not (0,0) then it works fine.
I just unchecked scroll enabled property of TextView in storyboard and its not giving that issue any more.
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];
}
}
This question already has answers here:
How can I make a UITextField move up when the keyboard is present - on starting to edit?
(98 answers)
Closed 9 years ago.
i have a UI textfield in a subview.I am adding it to a view .When the keyboard pops-up ,i want to animate the view to which i am adding this subview.I have written all my textfield delegate functions in my subview class only.So if i use Animate textfield function it doesn't move the parent view instead thy subview is animated....please help me
Try this:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
in the viewDidLoad
and then this
- (void)keyboardWillHide:(NSNotification *)aNotification
{
// the keyboard is hiding reset the table's height
NSTimeInterval animationDuration =
[[[aNotification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect frame = self.view.frame;
frame.origin.y += 160;
[UIView beginAnimations:#"ResizeForKeyboard" context:nil];
[UIView setAnimationDuration:animationDuration];
self.view.frame = frame;
[UIView commitAnimations];
}
- (void)keyboardWillShow:(NSNotification *)aNotification
{
// the keyboard is showing so resize the table's height
NSTimeInterval animationDuration =
[[[aNotification userInfo] objectForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect frame = self.view.frame;
frame.origin.y -= 160;
[UIView beginAnimations:#"ResizeForKeyboard" context:nil];
[UIView setAnimationDuration:animationDuration];
self.view.frame = frame;
[UIView commitAnimations];
}
in your view controller
Most likely you will have to change the value (160) that I put here based on your specific view
have you tried textFieldDidBeginEditing i don know whether you tried this or not and whether it is proper way or not. I used this it is working less lines to achieve
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
self.view.frame=CGRectMake(0, -300, 320, 700);
}
this will move your root view to top so your sub view automatically will move to top text field will not be hide behind keyboard and sorry i don have reputation to comment