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)
{
}];
}
Related
Here is the some piece of code I written for moving textview when user start typing in textview.
Here is the code,
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView{
if(textView == _textViewMsg || textView == _subjectView ){
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
// return YES;
}
return YES;
}
- (BOOL)textViewShouldEndEditing:(UITextView *)textView{
if(textView == _textViewMsg || textView == _subjectView ){
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
[self.view endEditing:YES];
}
return YES;
}
- (void)keyboardDidShow:(NSNotification *)notification
{
[self.view setFrame:CGRectMake(0,-50,320,460)];
}
-(void)keyboardDidHide:(NSNotification *)notification
{
[self.view setFrame:CGRectMake(0,60,320,520)];
}
When I click on textview (in Subject) view in moving and I am able to type in textview. It is working fine. (Image 1)
When I click on done button, while hiding i.e at the time of keyboard hide one black view is coming (check Image 2) for few seconds, and then again normal view come.
Edit:
Solution : #michael gives me solution just now
changing UIKeyboardDidHideNotification to UIKeyboardWillHideNotification it works for me.
New Problem Occurred :
When I start typing in first 2 textviews i.e Requester, firstname & lastname...I am not able to type in it, because it is moving up.
It happened because you changing frame of your view after keyboard did disappear, when system animation is completed. Quick fix: change UIKeyboardDidHideNotification to UIKeyboardWillHideNotification
But I want to point few other items in you code, if you don't mind.
1:
You are subscribing for notifications each time user begin editing. It means that when user start typing second time, you code will be triggered twice. It is much more more appropriate to subscribe for notifications on viewDidAppear and unsubscribe on viewWillDisappear:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear: animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShow:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardDidHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
2:
Then you changing your frame to constant value. But now apple support different keyboards with different size not to mention that keyboards size vary on different devices and in different possible modes. So it is much more wise to get the size of keyboard from notification:
- (void)keyboardDidShow:(NSNotification *)aNotification
{
CGRect keyboardFrame = [[aNotification.userInfo valueForKey: UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect overlap = CGRectIntersection( self.view.frame, keyboardFrame);
// Setup new frmae according to overlap, for example reduce size by half of overlap, and move up by another half
CGRect newFrame = self.view.frame;
newFrame.origin.y -= overlap.size.height / 2.0
newFrame.size.height -= overlap.size.height / 2.0
self.view.frame = newFrame;
}
3:
Animations: You can fetch keyboard animation duration from notification as well:
UIKeyboardAnimationDurationUserInfoKey:
NSTimeInterval duration = [[aNotification.userInfo valueForKey: UIKeyboardAnimationDurationUserInfoKey] doubleValue];
[UIView animateWithDuration:duration animations:^{
self.view.frame = newFrame;
}];
4
As it seems your working with scroll view (UITableView and UICollectioView are scroll view), you can instead of changing frame, change content inset (add empty space to bottom scroll view)
[self.view setContentInset:UIEdgeInsetsMake(0, 0, overlap.size.height, 0)];
5
Consider using AutoLayout.
adding observers should be there in viewdidload. try moving the code of adding observers for keyboard to viewdidload.
Edit
Instead of did... notification type check with will...
so replace UIKeyboardDidShowNotification with UIKeyboardWillShow and UIKeyboardDidHideNotification with UIKeyboardWillHide.
Use of IQKeyboardManager framework will help to handle keyboard hide and unhide issues,
Most efficient way for keyboard handling: IQKeyboardManager
Method 1:
You can use a Cocoa Pod file for IQKeyboardManager Library.
init the pod file.
pod 'IQKeyboardManager'
install Pod file.
Method 2:
Download IQKeyboardManger SDK from github and drag & drop inside your project file.
Swift code snippet:
import IQKeyboardManagerSwift
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
IQKeyboardManager.sharedManager().enable = true
return true
}
}
Objective-C Snippet:
#import <IQKeyboardManager/IQKeyboardManager.h>
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//ONE LINE OF CODE.
//Enabling keyboard manager(Use this line to enable managing distance between keyboard & textField/textView).
[[IQKeyboardManager sharedManager] setEnable:YES];
return true;
}
Hope this will help you to resolve your keyboard hide and unhide UI issues.
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];
}
I have a project that contains a UIScrollView and many UITextField inside it.
For the first time I select a UITextField, UIKeyboardWillShowNotification is called, which is fine. But whenever I select new UITextField (THE KEYBOARD IS STILL THERE), UIKeyboardWillShowNotification is called again !!!, which is weird.
I also set a symbolic breakpoint for [UIResponder resignFirstResponder] and I see that it is hit before and after UIKeyboardWillShowNotification is called !!!
The other thing is that UIKeyboardWillHideNotification is only called when I hit the "Done" button on the keyboard
I'm sure to not call any resignFirstResponder, becomeFirstResponder, endEditing anywhere. (I mean not call wrongly)
What can cause this problem ?
Here is the stacktrace
To workaround the problem, I used the following code to cancel the UIKeyboardWillShowNotification callback if the keyboard's frame is not changing.
func keyboardWillShow(notification: NSNotification) {
let beginFrame = notification.userInfo![UIKeyboardFrameBeginUserInfoKey]!.CGRectValue()
let endFrame = notification.userInfo![UIKeyboardFrameEndUserInfoKey]!.CGRectValue()
// Return early if the keyboard's frame isn't changing.
guard CGRectEqualToRect(beginFrame, endFrame) == false else {
return
}
...
}
For Swift 3/4:
func keyboardWillShow(notification: Notification) {
let userInfo = notification.userInfo!
let beginFrameValue = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)!
let beginFrame = beginFrameValue.cgRectValue
let endFrameValue = (userInfo[UIKeyboardFrameEndUserInfoKey] as? NSValue)!
let endFrame = endFrameValue.cgRectValue
if beginFrame.equalTo(endFrame) {
return
}
// Do something with 'will show' event
...
}
The problem is I set inputAccessoryView for the UITextField, and this cause UIKeyboardWillShowNotification being called again when new UITextField is selected
This article Working With Keyboard on iOS explains this well
Additional changes take place when we connect an external keyboard to
the iPad. In this particular case, the notification behavior depends
on the inputAccessoryView property of the control which was the reason
for displaying the keyboard.
If inputAccessoryView is not present or its height is equal to 0
points, no keyboard notifications are sent. My guess is that this is
because in this case, no visual changes take place in application.
Otherwise, all notifications behave as expected – which means they are
being sent as in the majority of cases when the keyboard is displayed
or hidden in a normal (not undocked or split) state.
Whenever new UITextField is selected, the OS needs to compute the frame for the keyboard again, and the following notifications are posted
UIKeyboardWillChangeFrameNotification
UIKeyboardWillShowNotification
UIKeyboardDidChangeFrameNotification
UIKeyboardDidShowNotification
The same applies for when the TextField loses its first responder status
Note that using the same View for inputAccessoryView will cause UIKeyboardWillShowNotification only called once
In general I find that many things can cause spurious UIKeyboardWillShow and UIKeyboardWillHide notifications. My solution is to use a property to track whether the keyboard is already showing:
func keyboardShow(_ n:Notification) {
if self.keyboardShowing {
return
}
self.keyboardShowing = true
// ... other stuff
}
func keyboardHide(_ n:Notification) {
if !self.keyboardShowing {
return
}
self.keyboardShowing = false
// ... other stuff
}
Those guards block exactly the spurious notifications, and all is well after that. And the keyboardShowing property can be useful for other reasons, so that it is something worth tracking anyway.
The best approach is to Add notification & remove it once your purpose is solve.
like this .
- (void)viewWillAppear:(BOOL)animated
{
// 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];
}
Now write your code for movement of views & textField in keyboardWillShow & revert them back to position in keyboardWillHide methods.
Also remove the observers
- (void)viewWillDisappear:(BOOL)animated
{
// unregister for keyboard notifications while not visible.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
You can also resign the responder when you press return key.
-(BOOL)textFieldShouldReturn:(UITextField *)textField {
[_txtFieldEmail resignFirstResponder];
[_txtFieldPassword resignFirstResponder];
return YES;
}
That should solve your issue.
For those not using inputAccessoryView but are still having problems, it may be due to using sensitive (password) fields. See this Stack Overflow post and answer: keyboardWillShow in IOS8 with UIKeyboardWillShowNotification
I have struggled with this, after half a day of searching around and experimenting I think this is the shortest and most reliable code. It is a mix of a number of answers most of which I forget where I found (parts of which are mentioned here).
My problem was a WKWebView which (when the user changed fields) would spawn a load of WillShow, WillHide, etc notifications. Plus I had problem with the external keyboard which still has the onscreen touch bar thing.
This solution uses the same animation code to "Open" and "Close" the keyboard, it will also cope with external keyboard being attached and custom keyboard views.
First register for the UIKeyboardWillChangeFrameNotification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
Then you simply need to map the changes to your view in however you do it (change a hight or bottom constraint constant).
- (void)keyboardWillChangeFrame:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
CGRect keyboardEnd = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
CGRect convertedEnd = [self.view convertRect:keyboardEnd fromView:nil];
// Convert the Keyboard Animation to an Option, note the << 16 in the option
UIViewAnimationCurve keyAnimation = [userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue];
// Change the Height or Y Contraint to the new value.
self.keyboardHeightConstraint.constant = self.view.bounds.size.height - convertedEnd.origin.y;
[UIView animateWithDuration:[userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]
delay:0.0
options:keyAnimation << 16
animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
The Animation to Option conversion seems to work (I can only find examples of it being used and not how/why), however, I am not convinced it will stay that way so it may be wise to use a "stock" option. It seems that the Keyboard uses some none specified Animation.
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
I have an application with multiple views that transitions to the main screen depending on which button was pressed. My current problem is that if the view is in the middle of animating then when the user selects another button then the whole layout becomes messed up. (ex: the views don't align with the screen meaning that they become a few pixels off)
What I would like to know is if there is a way to check if the view is currently animating and if so just have it animate to the last frame and skip anything in between. Below is a small piece of code that I have just tested based on what I have read on other user asked questions on SO:
-(IBAction)buttonPress:(id)sender
{
if([selectedView.layer.animationKeys count] > 0)
{
[selectedView.layer removeAllAnimations];
}
// Perform other calculations once the animation has stopped
}
There are many ways to do this but....
If you are using block animations you could set a "isAnimating" flag when the animation starts and set it again in the completion block. You could check the bool from anywhere and handle cases as needed.
As for needing code to execute after an animation occurs, but
// animation code in some method...
[UIView animateWithDuration:1.0
delay: 0.0
options: UIViewAnimationOptionCurveEaseIn
animations:^{
isAnimating = YES;
fooView.alpha = 0.0;
}
completion:^(BOOL finished){
isAnimating = NO;
[[NSNotificationCenter defaultCenter] postNotificationName:#"FooBeDone" object:nil userInfo:nil]
}];
-(IBAction)buttonPress:(id)sender {
if (isAnimating) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doBar:)
name:#"FooBeDone"
object:nil];
// possibly disable button to prevent multiple taps?
} else {
[self doBar];
}
}
- (void)doBar {
// do what needs to be done when when the animation is over
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"FooBeDone" object:nil];
// possibly enable button again
}
edit: I added more code to show a possible notification method. Creating extended loops in your IBAction will lock the user interface until the loop finishes and you can get back to the main run loop, so it's highly advised to avoid it. Notifications should give you the same effect but allow your main run loop to continue unhindered.