I'm currently trying to develop a chat feature for an iOS app. I've been playing around with the keyboard and everything seems to be working other than the animation.
When I press the keyboard the animation to move the view up is delayed by a second. I've made a video of it happening here.
My code is as follows:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
The selectors:
- (void)keyboardDidShow: (NSNotification *) notif{
NSDictionary *info = [notif userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
_currentKeyboardHeight = kbSize.height;
// Calculate free space between navigation bar and keyboard top to reposition the chat bubbles.
float areaHeight = screenHeight - (_currentKeyboardHeight + _footer.frame.size.height + 100);
if([Infrastructure_Connection ConnectivityCheck]){
[UIView animateWithDuration:0.2 delay:0 options:0 animations:^{
[_ChatBar setFrame:CGRectMake(posX, screenHeight-(_currentKeyboardHeight+height), width, height)];
bubbleTableMain.frame = CGRectMake(0,100, self.view.frame.size.width,areaHeight);
} completion:^(BOOL finished) {
[bubbleTableMain scrollBubbleViewToBottomAnimated:YES];
}];
} else {
[RegisterUser AlertMessage:#"Offline" Message:#"You're currently in offline mode."];
}
}
- (void)keyboardDidHide: (NSNotification *) notif{
int whereKeyboardEnds = self.view.frame.size.height-(195);
[UIView animateWithDuration:0.2 delay:0.0 options:0 animations:^{
bubbleTableMain.frame = CGRectMake(0,100, self.view.frame.size.width,whereKeyboardEnds);
[_ChatBar setFrame:CGRectMake(posX, 474, width, height)];
} completion:^(BOOL finished) { }];
}
The delays are on 0.0 and still it delays it by a second. Anyone have any idea as to how, for example, Whatsapp make their keyboard interaction so smooth and in time with the keyboard appearing?
=================== Edit ===================
So I've changed the following code and now for some reason, it doesn't run the animation. I've debugged it and it calls the selector correctly. But doesn't animate:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
and
- (void)keyboardWillShow:(NSNotification *)note {
UIViewAnimationCurve animationCurve = [[[note userInfo] valueForKey: UIKeyboardAnimationCurveUserInfoKey] intValue];
NSTimeInterval animationDuration = [[[note userInfo] valueForKey: UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView beginAnimations:nil context: nil];
[UIView setAnimationCurve:animationCurve];
[UIView setAnimationDuration:animationDuration];
[_ChatBar setFrame:CGRectMake(0, 10, _ChatBar.frame.size.width, _ChatBar.frame.size.height)];
[UIView commitAnimations];
}
You are using the keyboardDidShow notification which is called after the keyboard has finished its animation. Use the keyboardWillShow notification instead. That should do the trick.
Related
I'm trying to get the height of the iOS keyboard so that I can move my view up when the keyboard is animated. However, I keep getting the value 258 returned (which, isn't correct, as it pushes my view up too high?) Why is this happening? Code below:
ViewController.m
-(void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChange:) name:UIKeyboardWillChangeFrameNotification object:nil];
}
- (void)keyboardWillChange:(NSNotification *)notification {
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
self.keyboardHeight = keyboardFrameBeginRect.size.height;
}
- (void) animateTextView:(BOOL) up
{
const int movementDistance = self.keyboardHeight;
const float movementDuration = 0.3f;
int movement= movement = (up ? -movementDistance : movementDistance);
[UIView beginAnimations: #"anim" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.upView.frame = CGRectOffset(self.upView.frame, 0, movement);
[UIView setAnimationDidStopSelector:#selector(afterAnimationStops)];
[UIView commitAnimations];
}
To get height of keyboard, you should use UIKeyboardWillShowNotification or UIKeyboardDidShowNotification instead of UIKeyboardWillChangeFrameNotification.
-(void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChange:) name:UIKeyboardWillShowNotification object:nil];
}
If you use UITabBarController, you have to calculate frame without tab bar height. You can get tab bar height with the code below.
self.tabBarController.tabBar.frame.height
You should register UIKeyboardWillShowNotification for observing the keyboard height.
1) [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChange:) name:UIKeyboardWillShowNotification object:nil];
2) you can also try this code :
(void)keyboardWillChange:(NSNotification *)notification
{
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
}
I'm implementing real time chat in my app using firebase and and if the keyboard is shown and the user sends me 10 messages in a row, his messages will eventually start hiding behind my keyboard or go out below the tableview view (if the keyboard isn't shown). I believe this has to do with adjusting the view to adjust to the number of rows within the tableview. Ultimately, I need the latest cell to be right above the textfield (whether the keyboard is shown or not) every time I send a message or receive the latest message. Any tips? I took a look at firechat on github and they don't seem to have any code for this: https://github.com/firebase/firechat-ios/blob/master/Firechat/ViewController.m
- (void)viewWillAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]
addObserver:self selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification*)notification
{
[self moveView:[notification userInfo] up:YES];
}
- (void)keyboardWillHide:(NSNotification*)notification
{
[self moveView:[notification userInfo] up:NO];
}
- (void)moveView:(NSDictionary*)userInfo up:(BOOL)up
{
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey]
getValue:&keyboardEndFrame];
UIViewAnimationCurve animationCurve;
[[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey]
getValue:&animationCurve];
NSTimeInterval animationDuration;
[[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey]
getValue:&animationDuration];
// Get the correct keyboard size to we slide the right amount.
[UIView beginAnimations:nil context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:animationDuration];
[UIView setAnimationCurve:animationCurve];
CGRect keyboardFrame = [self.view convertRect:keyboardEndFrame toView:nil];
int y = keyboardFrame.size.height * (up ? -1 : 1);
self.view.frame = CGRectOffset(self.view.frame, 0, y);
[UIView commitAnimations];
}
I have a view with 1. Navigation bar 2.UITableView 3. UITextView.
When I start to edit the textView, a keyboard comes up and I need to animate the TextView and TableView up. I've implemented: https://stackoverflow.com/a/8704371/1808179, but that animated the entire view up, covering the navigation bar.
I tried individually animating the textView like:
- (void)keyboardWillShow:(NSNotification*)notification
{
CGRect chatTextFieldFrame = CGRectMake(chatTextField.frame.origin.x,chatTextField.frame.origin.y-218,chatTextField.frame.size.width,chatTextField.frame.size.height);
[UIView animateWithDuration:0.5 animations:^{ chatTextField.frame = chatTextFieldFrame;}];
}
But it doesn't animate, and it won't synchronously animate alongside the TableView.
What is the best way to animate the tableView and textView up without overlaying the navigation bar?
I generally use the follow snippet when tackling this problem.
Using a UITableView (which is just a subclass of UIScrollView) you should set contentInsets rather then just changing the frame each time. This is especially much nicer in iOS7 with a translucent keyboard.
- (void)viewDidLoad;
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)dealloc;
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
#pragma mark - Keyboard Notifications
- (void)keyboardWillShow:(NSNotification *)notification;
{
NSDictionary *userInfo = [notification userInfo];
NSValue *keyboardBoundsValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGFloat keyboardHeight = [keyboardBoundsValue CGRectValue].size.height;
CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
NSInteger animationCurve = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
UIEdgeInsets insets = [[self tableView] contentInset];
[UIView animateWithDuration:duration delay:0. options:animationCurve animations:^{
[[self tableView] setContentInset:UIEdgeInsetsMake(insets.top, insets.left, keyboardHeight, insets.right)];
[[self view] layoutIfNeeded];
} completion:nil];
}
- (void)keyboardWillHide:(NSNotification *)notification;
{
NSDictionary *userInfo = [notification userInfo];
CGFloat duration = [[userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
NSInteger animationCurve = [[userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey] integerValue];
UIEdgeInsets insets = [[self tableView] contentInset];
[UIView animateWithDuration:duration delay:0. options:animationCurve animations:^{
[[self tableView] setContentInset:UIEdgeInsetsMake(insets.top, insets.left, 0., insets.right)];
[[self view] layoutIfNeeded];
} completion:nil];
}
If any are wondering, this is how I did it:
- (void)keyboardWillShow:(NSNotification*)notification
{
CGRect chatTableViewFrame = CGRectMake(0,65,320,chatTableView.frame.size.height-180);
[UIView animateWithDuration:0.3 animations:^{ chatTableView.frame = chatTableViewFrame;}];
CGRect chatTextFieldFrame = CGRectMake(chatTextField.frame.origin.x,chatTextField.frame.origin.y-170,chatTextField.frame.size.width,chatTextField.frame.size.height);
[UIView animateWithDuration:0.3 animations:^{ chatTextField.frame = chatTextFieldFrame;}];
[self.chatTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.chat.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
And vice-versa for keyboardWillHide.
Write this line at desired events.
self.view.frame = [[UIScreen mainScreen] applicationFrame];
I need to scroll up my scrollView, when textFiewl is tapped that is bellow the virtual keyboard. I call [self.scrollView setContentOffset:scrollPoint animated:YES];. To get the screen's visible area, i obviously need the KB size.
I am familiar with
NSDictionary *info = [notification userInfo];
CGSize kbSize = [self.view convertRect:
[info[UIKeyboardFrameBeginUserInfoKey] CGRectValue]
fromView:nil].size;
however, it doesn't work for me, because when a user taps on possibly half-hidden textfield, i don't receive the keyboard notification.
So i call the method in textFieldDidBeginEditing:, which is called before keyboard will send message, and so i don't know the KB size on first tap.
So the question is: is possible to get the KB size, without invoking corresponding notification?
Programmaticaly, not hardcoding.
You are doing it wrong.
You need to also listen for the keyboard show/hide notifications and then adjust your screen.
Here is a sample skeleton code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(keyboardChangedStatus:) name:UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:#selector(keyboardChangedStatus:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[nc removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
#pragma mark - Get Keyboard size
- (void)keyboardChangedStatus:(NSNotification*)notification {
//get the size!
CGRect keyboardRect;
[[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardRect];
keyboardHeight = keyboardRect.size.height;
//move your view to the top, to display the textfield..
[self moveView:notification keyboardHeight:keyboardHeight];
}
#pragma mark View Moving
- (void)moveView:(NSNotification *) notification keyboardHeight:(int)height{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
[UIView setAnimationBeginsFromCurrentState:YES];
CGRect rect = self.view.frame;
if ([[notification name] isEqual:UIKeyboardWillHideNotification]) {
// revert back to the normal state.
rect.origin.y = 0;
hasScrolledToTop = YES;
}
else {
// 1. move the view's origin up so that the text field that will be hidden come above the keyboard (you need to adjust the value here)
rect.origin.y = -height;
}
self.view.frame = rect;
[UIView commitAnimations];
}
I use the code below atm. It is working fine as long as you don't quit the app (press home) while the keyboard is up. So I thought, simple, just resignFirstResponder in viewWillDissappear:(BOOL)animated, but that does not get called pressing home apparently...
So quick recap of the problem scenario:
Tap the textView -> keyboard comes up, view is shifted
Press home
Open app again -> keyboard still up, view is back where it was, so content not visible
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void) keyboardWillShow: (NSNotification*) aNotification;
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
CGRect rect = [[self view] frame];
rect.origin.y -= 60;
[[self view] setFrame: rect];
[UIView commitAnimations];
}
- (void) keyboardWillHide: (NSNotification*) aNotification;
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
CGRect rect = [[self view] frame];
rect.origin.y += 60;
[[self view] setFrame: rect];
[UIView commitAnimations];
}
You could add notification for enter background event, like the following code
- (void)viewDidLoad
{
//Add this
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(handleEnteredBackground:)
name: UIApplicationDidEnterBackgroundNotification
object: nil];
}
And in the function
- (void) handleEnteredBackground:(NSObject*)obj
{
//Adjust your views
}