Objective-C: detect keyboard size/origin *before* it opens? - ios

How is this done? I'm looking for iOS7/8 solutions. keyboardWillShow is not satisfactory because I need to resize a view based on the keyboard height before the keyboard actually shows.

keyboardWillShow is fired before the keyboard is shown. If that's not satisfactory for you, then you'll need to be smart about the keyboard size.
If the keyboard has never been shown on screen in your app before, you can make an educated guess by first checking for the device type and orientation and then having a quick lookup table of the default keyboard sizes. This will cover you 99% of the time.
In the event that the user has a custom keyboard in use that is not a standard size, you can use the keyboard size from keyboardWillShow, store it and the orientation (NSUserDefaults would work well here) and then reference the stored value the next time you need the size.
This wouldn't cover your needs every time because you wouldn't know which keyboard is going to be pulled up until keyboardWillShow is called. For example, you could replace the inputView on two different UITextField's with your own custom views; those views could be different sizes. You wouldn't know which one was going to be shown until keyboardWillShow would be called.
EDIT
There is another possibility...if you know the view that you want to show the keyboard for explicitly.
I added this to viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShowFirstTimeNotification:)
name:UIKeyboardWillShowNotification
object:nil];
[self.view addSubview:self.textField];
[self.textField becomeFirstResponder];
Then, add a method for handling that notification. This method should only be called once and then inside of it remove the notification so it's never called again.
- (void)keyboardWillShowFirstTimeNotification:(NSNotification*)notification {
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
NSLog(#"keyboardFrameBeginRectHeight: %f", keyboardFrameBeginRect.size.height);
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
[self.textField resignFirstResponder];
}
This will log the keyboard height without ever showing it on screen.
If you wanted to extend this further, you could subclass UITextField and UITextView to have properties for keyboard height for different orientations and then you could store that value directly in the text fields and text views. Then, you'd be able to have multiple input view sizes and know what they will be prior to showing them.

Currently the time for the keyboard to show is 0.3 seconds, but Apple may change that at any time. This is also true for the keyboard size. The default keyboard in portait mode is 216px in height and in landscape it is 162px, but that may also change at any time. If (for any reason) you need to find out the keyboard size you can do that pretty easily.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
// Read the userInfo for the key UIKeyboardFrameBeginUserInfoKey
-(void)keyboardWillShow:(NSNotification*)notification {
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
NSLog(#"%#", NSStringFromCGRect(keyboardFrameBeginRect));
}

Related

iPhone keyboard sizes

Is there a way to get the keyboard size programmatically before the keyboard is presented? In Objective-C
I need to set view.height constraint to be the same as keyboard.height programmatically. And it needs to happen before the keyboard is presented, so the view don't get this ugly constrain animation after the ViewController is presented.
I assume you present the keyboard by calling becomeFirstResponder on some UI component.
If the keyboard appears after your view is presented, you should check where that call is performed. Calling it in viewDidLoad or similarly early should cause the keyboard to be shown as the view animates in.
Your layout should also handle the keyboard changes properly. The keyboard size can change even after it's presented. For example the emoji/quick type keyboards are taller than the default keyboard.
You should perform your constraint changes in a combination of UIKeyboard[Will/Did]ShowNotification, UIKeyboard[Will/Did]HideNotification and UIKeyboardWillChangeFrameNotification. In your case, UIKeyboardWillShowNotification should do the trick.
The userInfo dictionary contains a lot of information about the keyboard. You find the final frame of the keyboard in UIKeyboardFrameEndUserInfoKey. If you animate the changes in your layout, you can use values in UIKeyboardAnimationCurveUserInfoKey and UIKeyboardAnimationDurationUserInfoKey to animate with the same animation as the keyboard.
- (void)viewDidLoad {
[super viewDidLoad];
// Don't forget to remove the observer when appropriate.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[self.textField becomeFirstResponder];
}
- (void)keyboardWillShow:(NSNotification *)notification {
CGFloat keyboardHeight = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
[self.viewHeightConstraint setConstant:keyboardHeight];
// You can also animate the constraint change.
}
Such setup will also work if the keyboard is presented from the get-go.

How to detect keyboard frame changes when dismiss the keyboard interactively?

Consider this scenario, I have a textview with keyboard Dismiss interactively set in storyboard, so when user scroll down and able to dismiss keyboard interactively.
I have constraints on the textview to bottom to make sure it is always fully displayed on the view.
Current problem is, when user gradually scroll down to dismiss the keyboard, I can not detect the keyboard frame changes. I tried UIKeyboardWillHideNotification and UIKeyboardWillChangeFrameNotification, they were only called after the keyboard dismissed.
So my question is, how can we detect keyboard frame changes simultaneously when dismiss the keyboard interactively?
If you want to observe keyboard frame changes even when the keyboard is being dragged you can use this: https://github.com/brynbodayle/BABFrameObservingInputAccessoryView
Basically you create a placeholder input view for keyboard (which sticks to the keyboard all the time, even while dragging) and you observe it's frame changes. Those changes are being returned in a block, so you get current frame of the keyboard all the time.
You shouldn't change the textView height to fit all view. Instead - you should change contentInset field so your textView will stay the same height and you won't have to bother about tracking frame of the interactive keyboard.
See answer here:
How do I scroll the UIScrollView when the keyboard appears?
In your viewDidLoad method add these line:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
the add these methods to your viewController
- (void)keyboardWillShow:(NSNotification *)notification
{
NSDictionary *userInfo = [notification userInfo];
CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
[UIView animateWithDuration:0.30
delay:0.0
options:(7 << 16) // This curve ignores durations
animations:^{
self.buttonsBottomConstraint.constant = keyboardSize.height - self.footerView.bounds.size.height + 4.0;
[self.view layoutIfNeeded];
}
completion:nil];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
[UIView animateWithDuration:0.30
delay:0.0
options:(7 << 16) // This curve ignores durations
animations:^{
self.buttonsBottomConstraint.constant = 0.0;
[self.view layoutIfNeeded];
}
completion:nil];
}

Is there any possible ways/delegates to identify the state of input accessory view of a UITextView's keyboard

In my native iOS app, I have a screen that contains a simple textview. I need to adjust the size/frame of the text view when keyboard appears. I've succeeded it with UIKeyboardDidShowNotification as below:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myKeyBoardIsOnScreen:) name:UIKeyboardDidShowNotification object:nil];
And setting the frame on:
- (void)myKeyBoardIsOnScreen:(NSNotification*)notification {
NSDictionary* keyboardInfo = [notification userInfo];
NSValue* keyboardFrameBegin = [keyboardInfo valueForKey:UIKeyboardFrameBeginUserInfoKey];
CGRect keyboardFrameBeginRect = [keyboardFrameBegin CGRectValue];
self.textView.frame = CGRectMake(self.textView.frame.origin.x, self.textView.frame.origin.y, self.textView.frame.size.width, self.view.frame.size.height-keyboardFrameBeginRect.size.height-self.textView.frame.origin.y);
}
Problem: This looks ok for the first moment. But later I realised that the frame of the keyboard is with the height of its accessory view included. So when I hide the accessory view by dragging it down, the textview appears to be broken.
Hence can anyone suggest me any possible ways/delegates to identify the state of input accessory view of a textview's keyboard (like: Input accessory view is shown/hidden,etc.)
NB: I need the accessory view. Hence I don't need to remove it.
Register as an observer for the UIKeyboardDidChangeFrameNotification to update your view's frame.
Apple Docs

I set scroll view offset to show text field hidden by keyboard. If the user scrolls while keyboard is show, scroll view snaps back down

As the title says, I have a UITextField inside a UIScrollView. When the keyboard is shown, I adjust the contentOffset of the scroll view so that the text field is hidden. The issue is if the text field is at the bottom of the scroll view. When the keyboard pops up, the scroll view adjusts as needed. But, if the user touches and scrolls the area above the keyboard, then the scroll view snaps back down. Intuitively, this makes sense because I've programatically over-scrolled the scroll view, but from a user perspective it is not nice.
What can I do about this? One thing I've thought of is to move the entire scroll view frame instead of setting the content offset. I don't know how to do this. I have the desired change in offset stored in a CGFloat. Can someone help?
You need to change the contentInset. The contentOffset is the current scroll position so when the user scrolls it gets reset.
An example of this can be found here: https://stackoverflow.com/a/16806736/78496
One thing you could do is listen to UIKeyboardWillShowNotification and UIKeyboardWillHideNotification system notifications to know when to modify the contentInset of your UIScrollView. You could do this at the viewWillAppear:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
Don't forget to remove yourself as an observer too,
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
When the keyboard will show or hide you can adjust the contentInset given the keyboard's height.
- (void)keyboardWillShow:(NSNotification *)notification {
CGRect keyboardEndFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
UIEdgeInsets scrollInsetWithKeyboard = UIEdgeInsetsMake(0, 0, 0, -keyboardEndFrame.height, 0)
self.scrollView.contentInset = scrollInsetWithKeyboard; // If you have a custom inset maybe now would be a good idea to save it so you can restore it later
}
- (void)keyboardWillHide:(NSNotification *)notification {
self.scrollView.contentInset = UIEdgeInsetsZero; // Or to whatever inset you had before
}
When those two methods are fired you could also animate the contentOffset if you'd like.
You should use this library : https://github.com/hackiftekhar/IQKeyboardManager
It is really awesome, you have only to add this lib in your project and it will manage all your textfields. You have zero line of code to do to implement this lib, it is automatic. I use it in all my project and it works fine everywhere (for textfield in a cell, tableview, scrollview...)

Managing vertical scrolling in a UIWebView

I try to get a content editable UIWebView with a "normal" scrolling operation, so that when the text cursor is going to be hidden by the keyboard, the UIWebView scrolls to prevent that. I read the "Managing the Keyboard" document from iOS developer library and got no result, using the scrollView property of my UIWebView. I also found numerous tricks on the web, using ScrollView properties/methods or javascript commands but cannot obtain a normal scrolling operation, like any NSTextView does on MacOS for example. Do you know any solution to this problem?
[EDITED] Got the solution, and created a method fired by a NSTimer. To get the caret Y position, one should get the selection of the active element, and insert a dummy node. Then getting the node.offsetTop property gives the caretY. Do not forget the remove the node...
Ok, here is the way Apple does it in their examples (I no longer remember what project name that was):
1.You register for keyboard notifications(the keyboard send a notification every time it shows up or it gets hidden):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
2.Get the keyboard's size (you are interested in the height) and scroll if that point is not contained in the view:
- (void)keyboardWasShown:(NSNotification*)aNotification
{
NSDictionary* info = [aNotification userInfo];
CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// If active text field is hidden by keyboard, scroll it so it's visible
CGRect aRect = self.view.frame;
aRect.size.height -= kbSize.height;
CGPoint point;
point = CGPointMake(0, activeTextField.frame.origin.y + activeTextField.frame.size.height);
if (!CGRectContainsPoint(aRect, point ))
{
CGPoint scrollPoint = CGPointMake(0.0, activeTextField.frame.origin.y+activeTextField.frame.size.height-kbSize.height);
[scrollView setContentOffset:scrollPoint animated:YES];
}
Don't forget do reverse things when hiding keyboard and remove the observers if you push to another view controller.
This example is used with text fields on a UIScrollView but I'm sure you can easily adapt it.
Personally if I also manage the web side, I prefer to create the webpages 320px wide, set my webView sizeToFit, add it on top of a UIScrollView and manage the scrolling from there. For textFields, I simply get the container for the selected textField using javascript and get it's coordinates.

Resources