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];
}
Related
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...)
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));
}
I have a UITextView (which is also a UIScrollView) which contains a bunch of text. In the screenshot there is more text underneath the keyboard. I can't scroll up to see that text - no matter what I do that text remains underneath the keyboard.
How can I fix things so that I can scroll to see all the text?
This works and it's pretty simple.
In .h
#property (weak, nonatomic) IBOutlet UITextView *tv;
#property CGSize keyboardSize;
In .m
- (void)viewDidLoad {
[super viewDidLoad];
// Register for keyboard notifications
[[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 {
// Get the keyboard size from the notification userInfo
NSDictionary *info = [aNotification userInfo];
self.keyboardSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// Adjust the content inset from the bottom by the keyboard's height
UIEdgeInsets contentInsets = UIEdgeInsetsMake(0, 0, self.keyboardSize.height, 0);
self.tv.contentInset = contentInsets;
}
- (void) keyboardWillHide: (NSNotification*) aNotification {
// Reset the content inset when the keyboard is dismissed
self.tv.contentInset = UIEdgeInsetsZero;
}
The scrollView has a property called contentSize which determines the area up to which the user can scroll. You would have to manually change this value to compensate the extra scroll space due to the keyboard.
What I suggest is to register for the notifications UIKeyboardWillHideNotification UIKeyboardWillShowNotification.
When the keyboard is about to show, the UIKeyboardWillShowNotification notification is fired and in the corresponding method add the keyboard height to the scroll contentSize height.
Similarly, deduct this height fom the scroll contentSize height in the UIKeyboardWillHideNotification notification.
Hope this helps! :)
To avoid all the manual resizing and stuff, I recommend using this great library - https://github.com/hackiftekhar/IQKeyboardManager. It will do all the hard work for you.
I had a iPad project, inside one of its view controllers, there are two TextFields inside a panel view (Views are build in Storyboard). What I would like to achieve is when any of those textfield become first responder (i.e. Keyboard Appears), the panel view will move up and if keyboard disappear it will move down to origin position.
Once I test move up, I found the view will automatically move back to origin position after keyboard disappear, but I didn't write any code to do that.
Code to move up:
- (void)moveUpTextFields {
if ([self.emailTextField isFirstResponder] || [self.passwordTextField isFirstResponder]) {
CGRect frame = self.textFieldPanel.frame;
frame.origin.y -= 50;
self.textFieldPanel.frame = frame;
}
}
So I would like to figure out how does that happen, and how should I achieve my goal (i.e. Move up if keyboard appear and back to original position)?
UPDATE:
To achieve my goals, it should use Keyboard Notification. Register these notifications in 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];
}
I did this in another project long time ago, just didn't remember it at the first place :(
You have to listen to the keyboard events in the Notification Center, and lucky you, this is a functionality almost every app needs, so check this :)
https://github.com/aceontech/KBKeyboardHandler
It does exactly what you need but I prefer if you would understand how it behaves (registering for system notifications).
It's also available through cocoapods if you want
pod 'KBKeyboardHandler'
OK, so the weird behaviour is because when Keyboard Appear or Disappear, it will relayout view controller's sub views and trigger -(void)viewDidLayoutSubviews and reposition view back to StoryBoard's position. If the view is created by code then this won't be a issue.
In addition to add notifications for KeyBoard, we also need to reposition the view in viewDidLayoutSubviews since the notification triggered before layout subviews.
- (void)keyboardWillShow:(NSNotification *)notification {
CGSize keyboardSize = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
CGFloat y = self.view.frame.size.height - keyboardSize.height - self.textFieldPanel.frame.size.height - 5;
CGRect frame = self.textFieldPanel.frame;
panelViewYOffset = frame.origin.y - y;
frame.origin.y -= panelViewYOffset;
// Since it will layout subviews in viewDidLayout, why we set frame here?
// I tried to move this line, turns out the panel will move up but the animation is gone
// So this line will have a nice liner animation curve but I don't why, may be someone can explain it.
self.textFieldPanel.frame = frame;
}
- (void)keyboardWillHide:(NSNotification *)notification {
CGRect frame = self.textFieldPanel.frame;
frame.origin.y += panelViewYOffset;
self.textFieldPanel.frame = frame;
panelViewYOffset = 0;
}
In viewDidLayoutSubviews:
- (void)viewDidLayoutSubviews {
CGRect frame = self.textFieldPanel.frame;
frame.origin.y -= panelViewYOffset;
self.textFieldPanel.frame = frame;
}
Like I mentioned, this approach only works for view from StoryBoard (Interface Builder), if self.textFieldPanel is created by code, then the code in viewDidLayoutSubviews should be removed.
I'm using a textview and noticed that iOS 7 leaves a top margin by default. See image in the following
I read different posts in which the most common solution is to use:
[textViewTest setContentInset:UIEdgeInsetsMake(<#CGFloat top#>, <#CGFloat left#>, <#CGFloat bottom#>, <#CGFloat right#>)];
But those insets are just a custom solution for a particular device, textview, font size, and so on. Therefore, there are no specific insets applicable to any solution... even worst, I would have to programmatically define different insets to account for all iOS devices and orientations.
Good news is that I found that whenever the textview becomes the first responder and keyboard is shown on screen, this top margin disappears even after keyboard has gone. By the way, I'm resizing contentInset on UIKeyboardDidShowNotification and UIKeyboardWillHideNotification.
See image when keyboard did show:
See image when keyboard has gone:
Is there a way to simulate keyboard show and hide? So that content inset disappears as explain above.
I have already tried making textview become first responder and then resign it, but for this approach the user would have to see the whole keyboard show-hide animation.
Thanks in advance!
My code below:
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleKeyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleKeyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
if(self.topMarginIsAlreadyResized == NO) {
[self.myTextView becomeFirstResponder]; // Keyboard will show to eliminate top margin when view appears
}
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)handleKeyboardDidShow:(NSNotification *)notification {
if(self.topMarginIsAlreadyResized == NO) {
self.topMarginIsAlreadyResized = YES; // Once that keyboard has shown when view appears, we should hide it manually
[self.myTextView resignFirstResponder];
}
NSValue *keyboardRectAsObject = [[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey];
CGRect keyboardRect = CGRectZero;
[keyboardRectAsObject getValue:&keyboardRect];
self.myTextView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, keyboardRect.size.height, 0.0f);
}
- (void)handleKeyboardWillHide:(NSNotification *)notification {
self.myTextView.contentInset = UIEdgeInsetsZero;
}
This happens because yor view controller has set the the property automaticallyAdjustsScrollViewInsets to YES, if you set it to NO everything will be fine. See this question and the accepted answer for more info.