UIKeyboardWillChangeFrameNotification called BEFORE textViewDidBeginEditing, but AFTER textFieldDidBeginEditing? Why? - ios

I have the following observer in my ViewController.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
This view controller also conforms to the UITextFieldDelegate and UITextViewDelegate and implements textFieldDidBeginEditing and textViewDidBeginEditing.
Now, here's the weird part.
If you tap on the UITextField, the order of calls is textFieldDidBeginEditing AND THEN keyboardWillChangeFrame:.
If you tap on the UITextView, the order of calls is keyboardWillChangeFrame AND THEN 'textViewDidBeginEditing'.
Anyone not see a problem with this? Shouldn't text_____DidBeginEditing be called first no matter whether it's a Field or View. Why is this?
It's leading to weird animation issues. I need it to be consistent one way or the other.

I believe you can use the UIKeyboardWillShowNotification, and something like the above code for what you are trying to do:
- (void)keyboardWillShowNotification:(NSNotification*)notification {
NSDictionary *info = notification.userInfo;
CGRect r = [info[UIKeyboardFrameEndUserInfoKey] CGRectValue];
r = [self.view convertRect:r fromView:nil];
UIViewAnimationCurve curve = [info[UIKeyboardAnimationCurveUserInfoKey] integerValue];
double duration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:curve];
[UIView setAnimationDuration:duration];
/* view changes */
[UIView commitAnimations];
}

Use UIKeyboardDidChangeFrameNotification instead of UIKeyboardWillChangeFrameNotification.

Related

How to Preserve state of UIView Controller when it come back from Background

I have created one login Page which have two uiTextField and Login Button. When you try to login Keyboard hide textfield and button, So I have used below code to move up and down view controller from textfield delegate methods.
-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
int movementDistance = -130; // tweak as needed
float movementDuration = 0.3f; // tweak as needed
int movement = (up ? movementDistance : -movementDistance);
[UIView beginAnimations: #"animateTextField" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
It works fine. But when i close app by home button and restart again it not preserve view controller position, Keyboard is still display but position of view controller changed to default.
Declare a NSNotification in ViewDidLoad of your View Controller class,when the application will become active it will call your desire method.
-(void)viewDidLoad
{
[[NSNotificationCenter defaultCenter]addObserver:self
selector:#selector(refreshViewOnActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
// write rest of your code
}
- (void)refreshViewOnActive:(NSNotification *)notification {
if( [textField isFirstResponder]) // check whether keyboard is open or not / or editing is enabled for your textfeild
{
[self animateTextField:textField up:true]; //call your desired method
}
}

move view to reset frame and keyboard at the same time

I have view to type message as shown below.
Now, when I type message, the keyboard appears and the box should move just above keyboard as shown in figure below.
my problem
They keyboard animation and view animation occur at different time. Keyboard appears first and then view appears. Even if i tried to set animation time to any, they occur at different time.
How should I solve my problem?
Please, suggest me way to solve it so that keyboard and view animates to show as if they are of same view. Both animation should occur at exact time so that they look like same view appeared at a time.
what i tried
my view did load has following code
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification object:nil];
now keyboard show function look like
- (void)keyboardWillShow:(NSNotification *)note{
NSDictionary* keyboardInfo = [note userInfo];
CGFloat duration = [[keyboardInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey] floatValue];
[UIView animateWithDuration:duration animations:
^{
//chat_typingView is name for typing view
chat_typingView.frame = CGRectMake(chat_typingView.frame.origin.x,
238,
chat_typingView.frame.size.width,
chat_typingView.frame.size.height);
}
maybe you should check if your chat_typinfView is First responder before do the animation and disable Autolayout (very important).
if ([chat_typingView isFirstResponder]) {
// Do the animation
}
PS is recommendable to subscribe to the notification on viewWillApper instead of viewDidLoad
I have a similar setup in one of my apps and I do the following:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardShowed:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
/*** FOR AUTOLAYOUT MODIFICATIONS & ADDITIONS # RUNTIME ***/
self.defaultViewFrame = self.myView.frame
}
- (void) keyboardShowed:(NSNotification*)notification {
//GET KEYBOARD FRAME
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
//CONVERT KEYBOARD FRAME TO MATCH THE OUR COORDINATE SYSTEM (FOR UPSIDEDOWN ROTATION)
CGRect convertedFrame = [self.view convertRect:keyboardFrame fromView:self.view.window];
//..... do something with the convertedFrame (in your case convertedFrame.origin.y)
}
- (void) keyboardHidden:(NSNotification*)notification {
//RESTORE ORIGINAL STATE
[UIView transitionWithView:self.view
duration:.3f
options:UIViewAnimationOptionCurveLinear
animations:^{
self.myView.frame = self.defaultViewFrame;
}
completion:nil];
}

iOS- Change of UITextField height and movement of Buttons based on keyboard display

I am having an view in iOS where there is an UITextfield and 3 UIbuttons below the UITextField. Please see below for the pic. When this view is launched, the default state is STATE1.(Please see image). The keyboard is visible by default. Now when I dispose the keyboard, I wish to have the edittext resized and occupy the whole screen as shown in STATE2.
I am not sure how to accomplish this. I have the height of the UITextfield hardcoded to some dp based on the target device. I believe this has to be changed and it has to dynamically occupy the screen based on the screen size.
Can anyone help me accomplish this. Please consider the button is like a tail to the edit text. This clings to the edit text no matter the keyboard is visible or not. Thanks
You can use TPKeyboardAvoiding library and achieve this what you want.
First you have to use UITextView instead of UITextField
The register your class for
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
Implement this methods
- (void)keyboardWillHide:(NSNotification *)notification {
[self resizeViewWithOptions:[notification userInfo]];
}
- (void)keyboardWillShow:(NSNotification *)notification {
[self resizeViewWithOptions:[notification userInfo]];
}
- (void)resizeViewWithOptions:(NSDictionary *)options {
NSTimeInterval animationDuration;
UIViewAnimationCurve animationCurve;
CGRect keyboardEndFrame;
[[options objectForKey:UIKeyboardAnimationCurveUserInfoKey] getValue:&animationCurve];
[[options objectForKey:UIKeyboardAnimationDurationUserInfoKey] getValue:&animationDuration];
[[options objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:animationCurve];
[UIView setAnimationDuration:animationDuration];
CGRect viewFrame = self.view.frame;
CGRect keyboardFrameEndRelative = [self.view convertRect:keyboardEndFrame fromView:nil];
NSLog(#"keyboardFrameEndRelative: %#", NSStringFromCGRect(keyboardFrameEndRelative));
// Now set Frame for UITextView and UIbuttons frame here
}
Try like this even simpler first add those controls what you want which looks like in state 1
in viewDidAppear add the following function
-(void)viewDidAppear:(BOOL)animated
{
[self.Textfield1 becomeFirstResponder];
}
so it will load keyboard when the view loads
then add this lines to .m to close the keyboard press return it will hide keyboard
-(BOOL) textFieldShouldReturn:(UITextField *)textField{
//Write your coding for resizing size dynamically to show look like state 2
//Here check version of device to set as per height
textfield.frame=CGRectMake(0,0,320,400);
button1.frame=CGRectMake(0,400,320,400);
[textField resignFirstResponder];
return YES;
}
don forget to add delegate for textfield

View that moves along with keyboard

I have an iOS app with a UIScrollView that basically looks like the Messages.app: content on the screen and on the bottom a text view and a button to add more content. When the keyboard appears the text view and button move up correctly.
I've set keyboardDismissMode so that dragging the keyboard down makes it disappear but during the process of the dragging, as the keyboard is moving down, how can I update my views' locations on screen to stay attached to it? It seems that the keyboard will change frame notification isn't fired during this process.
What's the "right" way of doing this?
Edit: I have a hunch it might be doable using an input view/accessory view, but not sure that's the right direction to go.
Pretty sure this behavior requires SPI. BUT: You could walk the view/window hierarchy, find the keyboard window, and apply a transform to it to move it during your interaction. (window.transform=)
Not sure what to do when your interaction ends however--maybe manually animate the keyboard away (using the above technique) to finish the keyboard hide, then when it's hidden, resign first responder without animating.
EDIT:
Sorry, I thought you wanted to move the keyboard, but if you want to observe the keyboards position, you can use KVO for that (as #KudoCC suggested)
static void * __keyboardCenterKVOContext = & __keyboardCenterKVOContext ;
-(void)setUpKeyboardObserver
{
UIView * keyboardView = ...?
[ keyboardView addObserver:self forKeyPath:#"center" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:__keyboardCenterKVOContext ] ;
}
Then implement the KVO observer:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ( context == __keyboardCenterKVOContext )
{
CGPoint newKeyboardCenter = [ [ change valueForKey:NSKeyValueChangeNewKey ] pointValue ] ;
// .. handle new keyboard position here ...
}
else
{
[ super observeValueForKeyPath:keyPath ofObject:object change:change context:context ] ;
}
}
Your KVO observer will be run every time the keyboard view changes it's center property. You might also try observing the keyboard window's frame and/or transform properties.
I have some code to help with KVO observing here, which might help you: https://gist.github.com/nielsbot/6873377
(The reason you might want to use my helper code is because it automatically removes KVO observation from objects that are being deallocated. This requires some run time fiddling, but in your case you don't really know when the keyboard window will be deallocated. Although maybe you can tear down KVO in your keyboardWillHide handler)
You can try notification to move your content when keyboard disappear and appear:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(keyboardDisappeared) name:UIKeyboardWillHideNotification object:nil];
[center addObserver:self selector:#selector(keyboardAppeared) name:UIKeyboardWillShowNotification object:nil];
-(void) keyboardDisappeared
{
[UIView animateWithDuration:1.0 delay:0 options:yourAnimationOption animations:^
{
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y+100(same as you do in keyboardAppeared), self.view.frame.size.width, self.view.frame.size.height);
} completion::^(BOOL finished)
{
}];
}
-(void) keyboardAppeared
{
[UIView animateWithDuration:1.0 delay:0 options:yourAnimationOption animations:^
{
self.view.frame = CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y-(as much you want), self.view.frame.size.width, self.view.frame.size.height);
} completion::^(BOOL finished)
{
}];
}

editable webView getting scrolled to top by itself

I have some text displayed in an editable webView. As soon as I scroll it down and touch somewhere to edit the rendered text, it scrolls to the top itself and the keyboard appears and hence I have to scroll it down again for editing. Is there a way to prevent webView from doing that?
Got the same problem and still looking for normal solution of this weird behavior.
We still cannot prevent UIWebView from doing this, and if you look at Evernote application on iPad, you'll see the same issue there, unfortunately :(
The only thing we could do on this is to save contentOffset of UIWebView when keyboard is shown and restore if after keyboard is opened.
This will look like:
//register your controller for keyboard notifications
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWasShown:) UIKeyboardDidShowNotification object:nil];
Then you will need to handle keyboard notification like:
- (void)keyboardWillShow:(NSNotification *)aNotification {
// scroll view will scroll to beginning, but we save current offset
[_yourViewWithWebView saveOffset];
...
}
After that you will need to handle event when keyboard was shown:
- (void)keyboardWasShown:(NSNotification*)aNotification{
...
// scroll view scrolled to beginning, but we restore previous offset
[_yourViewWithWebView restoreOffset];
}
Accordingly in your view which contains UIWebView you'll need to implement:
static CGPoint editableWebViewOffsetPoint;
- (void) saveOffset{
editableWebViewOffsetPoint = yourWebView.scrollView.contentOffset;
}
- (void) restoreOffset{
//just use animation block to have scroll animated after jumping to top and back to old position
[UIView animateWithDuration:.2
delay:0
options:UIViewAnimationOptionCurveEaseIn
animations:^{
yourWebView.scrollView.contentOffset = editableWebViewOffsetPoint;
}
completion:nil];
}
Hope in general this will help you to solve your problem at least partially.
If someone will help us to prevent UIWebView scrolling to top each time keyboard is displayed, I'd appreciate this deeply.
UIWebView.scrollView.scrollsToTop = NO; does not help.
Disabling scrolling before showing keyboard and enabling it after keyboard is displayed also didn't work.
Also in future you will face problem with editing text when cursor is not in visible area of UIWebView - and it does not scroll itself automatically to make cursor visible. We have solved that problem, but I am in progress of creating detailed and readable tutorial of how we've done this. If you already solved this problem, I'd appreciate to look at your solution :)
PS: http://www.cocoanetics.com/2011/01/uiwebview-must-die/
Thank you,
Sergey N.
A method that works rather well is temporarily disabling setContentOffset: on UIScrollView, while the keyboard is being shown. This is a little hackish though, so it may cause other issues instead, in some situations.
As in #Sergey N.'s response, register for the keyboard notifications, but instead of storing/restoring contentOffset, use these:
- (void)keyboardWillShow:(NSNotification *)aNotification {
[self disableMethod:#selector(setContentOffset:) onClass:[UIScrollView class]];
}
- (void)keyboardWasShown:(NSNotification *)aNotification {
[self enableMethod:#selector(setContentOffset:) onClass:[UIScrollView class]];
}
Somewhere else in the class (or in another class, as long as you replace self in above calls), place these:
-(void)swizzleMethod:(SEL)origSel from:(Class)origClass toMethod:(SEL)toSel from:(Class)toClass{
Method origMethod = class_getInstanceMethod(origClass, origSel);
Method newMethod = class_getInstanceMethod(toClass, toSel);
method_exchangeImplementations(origMethod, newMethod);
}
-(void)disableMethod:(SEL)sel onClass:(Class)cl{
[self swizzleMethod:sel from:cl toMethod:#selector(doNothing) from:[self class]];
}
-(void)enableMethod:(SEL)method onClass:(Class)cl{
[self swizzleMethod:#selector(doNothing) from:[self class] toMethod:method from:cl];
}
-(void)doNothing{
}
This prevents the webview from scrolling to top in the first place, so it won't show that bad animation, however, in some situations it may cause some problems (e.g have more input controls in the view holding the webview). Tested this successfully in iOS 5.0+.
In iOS 6.0 the scrolling to top seems to be fixed, so no workaround is necessary.
Functions for "editing text when cursor is not in visible area" problem.
- (void)keyboardWasShown:(NSNotification *)aNotification {
//if(self.navigationController.viewControllers objectAtIndex:([self.navigationController.viewControllers count]-1)==self.)
NSLog(#"keyboardshown");
if (keyboardshown)
return;
keyboardshown=YES;
NSDictionary* userInfo = [aNotification userInfo];
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
CGRect newFrame = self.textView.frame;
CGRect keyboardFrame = [self.textView convertRect:keyboardEndFrame toView:nil];
newFrame.size.height -= keyboardFrame.size.height;
[UIView beginAnimations:#"ResizeForKeyboard" context:nil];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.3];
[self.textView setFrame:newFrame];
[UIView commitAnimations];
}
- (void)keyboardWasHidden:(NSNotification *)aNotification {
if (!keyboardshown)
return;
keyboardshown=NO;
NSDictionary* userInfo = [aNotification userInfo];
CGRect keyboardEndFrame;
[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] getValue:&keyboardEndFrame];
CGRect newFrame = self.textView.frame;
CGRect keyboardFrame = [self.textView convertRect:keyboardEndFrame toView:nil];
newFrame.size.height += keyboardFrame.size.height;
[UIView beginAnimations:#"ResizeForKeyboard" context:nil];
[UIView setAnimationDuration:0.3];
self.textView.frame = newFrame;
[UIView commitAnimations];
}
Actually keyboardWasShown will not always be invoked especially when user has BT keyboard connected and virtual one can be hidden/shown by Eject key. We have implemented our own class like:
#implementation KeyboardUtils
+ (CGRect) convertRect:(CGRect)rect toView:(UIView *)view {
UIWindow *window = [view isKindOfClass:[UIWindow class]] ? (UIWindow *) view : [view window];
return [view convertRect:[window convertRect:rect fromWindow:nil] fromView:nil];
}
/**
* This is working but deprecated solution
* Based on UIKeyboardCenterBeginUserInfoKey and UIKeyboardCenterEndUserInfoKey which are deprecated since iOS 3.2
*/
+ (BOOL)checkKeyboardOnDisplayCenterBegin:(CGRect)centerBegin centerEnd:(CGRect)centerEnd{
CGRect mainScreen = [UIApplication currentBounds];
BOOL isKeyboardOnDisplay = CGRectContainsPoint(mainScreen, centerEnd.origin);
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:isKeyboardOnDisplay] forKey:#"isKeyboardOnDisplay"];
[[NSUserDefaults standardUserDefaults] synchronize];
return isKeyboardOnDisplay;
}
/**
* This method allows to verify if software keyboard is currently present on screen for the application
* Allows to handle undocked, split states of keyboard, as well as connected Bluetooth keyboard.
* Needed to adjust UI - scrolling and insets for editable parts of the app, as well as avoid application be beneath open keyboard
*/
+ (BOOL)checkKeyboardOnDisplayBeginFrame:(CGRect)frameBegin endFrame:(CGRect)frameEnd{
CGRect mainScreen = [UIApplication currentBounds];
UIView *firstView = [[(AppDelegate *)[[UIApplication sharedApplication] delegate] window].subviews objectAtIndex:0];
CGRect convertedEndFrame = [KeyboardUtils convertRect:frameEnd toView:firstView];
BOOL isKeyboardOnDisplay = CGRectContainsRect(mainScreen, convertedEndFrame);
[[NSUserDefaults standardUserDefaults] setValue:[NSNumber numberWithBool:isKeyboardOnDisplay] forKey:#"isKeyboardOnDisplay"];
[[NSUserDefaults standardUserDefaults] synchronize];
return isKeyboardOnDisplay;
}
+ (BOOL)checkKeyboardOnDisplayFromNotification:(NSNotification *)aNotification{
BOOL isKeyboardOnDisplay = [KeyboardUtils checkKeyboardOnDisplayBeginFrame:[[aNotification.userInfo valueForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]
endFrame:[[aNotification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]];
return isKeyboardOnDisplay;
}
Then you can use it like:
- (void)keyboardWillChangeFrame:(NSNotification*)aNotification{
[KeyboardUtils checkKeyboardOnDisplayFromNotification:aNotification];
}
Where keyboardWillChangeFrame is selector-observer for:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
In such a way you are saving state of your keyboard (if it is shown as docked one and really present on display, and BT keyboard is not used) into NSUserDefaultSettings. In your handlers which listen to keyboard notifications or orientation changes you should check this key value from the defaults.
One more additional method is [UIApplication currentBounds];
It is present in the app by extending it with category like: (.h file)
#import <UIKit/UIKit.h>
#interface UIApplication (AppDimensions)
+(CGSize) currentSize;
+(CGRect) currentBounds;
+(CGSize) sizeInOrientation:(UIInterfaceOrientation)orientation;
#end
.m file:
#import "UIApplication+AppDimensions.h"
#implementation UIApplication (AppDimensions)
+(CGSize) currentSize
{
return [UIApplication sizeInOrientation:[UIApplication sharedApplication].statusBarOrientation];
}
+(CGRect) currentBounds{
CGRect bounds = [UIScreen mainScreen].bounds;
bounds.size = [UIApplication currentSize];
return bounds;
}
+(CGSize) sizeInOrientation:(UIInterfaceOrientation)orientation
{
CGSize size = [UIScreen mainScreen].bounds.size;
UIApplication *application = [UIApplication sharedApplication];
if (UIInterfaceOrientationIsLandscape(orientation))
{
size = CGSizeMake(size.height, size.width);
}
if (application.statusBarHidden == NO)
{
size.height -= MIN(application.statusBarFrame.size.width, application.statusBarFrame.size.height);
}
return size;
}
#end
Hope this will help anyone who is concerned about handling presence of keyboard on the screen.

Resources