Animate UIView along keyboard appear animation - ios

I am using UIKeyboardWillShowNotification and UIKeyboardWillHideNotification to animate a view along the keyboard appear animation using UIKeyboardAnimationDurationUserInfoKey, UIKeyboardAnimationCurveUserInfoKey and UIKeyboardFrameEndUserInfoKey.
Everything works fine, as long as the elements start position is in the bottom of the screen. My element (input box in the screenshot) starts above the UITabBarController, so if my animation starts there is gap between keyboard and UITextField, which shrinks along the animation, till it reaches its end.
What I`m searching for is something like: "Animate with same animation curve, but start the moving, if keyboard reaches my maxY position".
If I would add a delay for starting the animation it would not be correct with the easing and this may break in future iOS releases.
It would be great if you share your ideas with me. :-)

There are typically two approaches you might use to keep a view above the keyboard as it animates into place. As you know, the first is to listen for the UIKeyboardWillShowNotification and use the accompanying duration/curve/frame values in the userData to help you position and animate your view above the keyboard.
A second approach is to supply an inputAccessoryView for the view (UITextField, here) that is invoking the keyboard. (I realize this won't provide the effect you're asking for, which is to "push" the toolbar/textfield up once the keyboard runs into it. But more on this later.) iOS will parent your inputAccessoryView to the view that also parents the keyboard and animate them in together. In my experience this provides the best-looking animation. I don't think I've ever had perfect animation using the UIKeyboardWillShowNotification approach, especially now in iOS7 where there's a little bounce at the end of the keyboard animation. There's probably a way with UIKit Dynamics to apply this bounce to your view too, but making it perfectly in sync with the keyboard would be hard.
Here's what I've done in the past for a scenario similar to yours: there is bottom-positioned UIToolbar having a UITextField in a customView bar button item for input. In your case this is positioned above a UITabBar. The ITextField has a custom inputAccessoryView set, which is another UIToolbar with another UITextField.
When the user taps into the text field and it becomes first responder, the keyboard animates into place with the 2nd toolbar/textfield along with it (and this transition looks very nice!). When we notice this happening we transition the firstResponder from the first text field to the second such that it has the blinking caret once the keyboard is in place.
The trick is what to do when you determine its time to end editing. First, you have to resignFirstResponder on the second text field, but if you're not careful then the system will pass first responder status back to the original text field! So you have to prevent that, because otherwise you'll be in an infinite loop of passing forward the first responder, and the keyboard will never dismiss. Second, you need to mirror any text input to the second text field back to the first text field.
Here's the code for this approach:
#implementation TSViewController
{
IBOutlet UIToolbar* _toolbar; // parented in your view somewhere
IBOutlet UITextField* _textField; // the customView of a UIBarButtonItem in the toolbar
IBOutlet UIToolbar* _inputAccessoryToolbar; // not parented. just owned by the view controller.
IBOutlet UITextField* _inputAccessoryTextField; // the customView of a UIBarButtonItem in the inputAccessoryToolbar
}
- (void) viewDidLoad
{
[super viewDidLoad];
_textField.delegate = self;
_inputAccessoryTextField.delegate = self;
_textField.inputAccessoryView = _inputAccessoryToolbar;
}
- (void) textFieldDidBeginEditing: (UITextField *) textField
{
if ( textField == _textField )
{
// can't change responder directly during textFieldDidBeginEditing. postpone:
dispatch_async(dispatch_get_main_queue(), ^{
_inputAccessoryTextField.text = textField.text;
[_inputAccessoryTextField becomeFirstResponder];
});
}
}
- (BOOL) textFieldShouldBeginEditing: (UITextField *) textField
{
if ( textField == _textField )
{
// only become first responder if the inputAccessoryTextField isn't the first responder.
return ![_inputAccessoryTextField isFirstResponder];
}
return YES;
}
- (void) textFieldDidEndEditing: (UITextField *) textField
{
if ( textField == _inputAccessoryTextField )
{
_textField.text = textField.text;
}
}
// invoke this when you want to dismiss the keyboard!
- (IBAction) done: (id) sender
{
[_inputAccessoryTextField resignFirstResponder];
}
#end
There's one final possibility I can think of. The approach above has the drawback of two separate toolbars/textfields. What you ideally want is just one set of these, and you want it to appear that the keyboard "pushes" them up (or pulls them down). In reality the animation is fast enough that I don't think most people would notice there are two sets for the above approach, but maybe you don't like that..
This final approach listens for the keyboard to show/hide, and uses a CADisplayLink to synchronize animating the toolbar/textfield as it detects changes in the keyboard position in real time. In my tests it looks pretty good. The main drawback I see is that the positioning of the toolbar lags a tiny bit. I'm using auto-layout and changing over to traditional frame-positioning might be faster. Another drawback is there is a dependency on the keyboard view hierarchy not changing dramatically. This is probably the biggest risk.
There's one other trick with this. The toolbar is positioned in my storyboard using constraints. There are two constraints for the distance from the bottom of the view. One is tied to the IBOutlet "_toolbarBottomDistanceConstraint", and this is what the code uses to move the toolbar. This constraint is a "vertical space" constraint with a "Equal" relation. I set the priority to 500. There is a second parallel "vertical space" constraint with a "Greater than or equal" relation. The constant on this is the minimum distance to the bottom of the view (above your tab bar, for example), and the priority is 1000. With these two constraints in place I can set the toolbars distance-from-bottom to any value I like, but it will never drop below my minimum value. This is key to making it appear that the keyboard is pushing/pulling the toolbar, but having it "drop off" the animation at a certain point.
Finally, perhaps you could make a hybrid of this approach with what you've already got: use a CADisplayLink callback to detect when the keyboard has "run into" your toolbar, then instead of manually positioning the toolbar for the remainder of the animation, use a real UIView animation to animate your toolbar into place. You could set the duration to be the keyboard-display-animation-duration minus the time already transpired.
#implementation TSViewController
{
IBOutlet UITextField* _textField;
IBOutlet UIToolbar* _toolbar;
IBOutlet NSLayoutConstraint* _toolbarBottomDistanceConstraint;
CADisplayLink* _displayLink;
}
- (void) dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver: self];
}
- (void) viewDidLoad
{
[super viewDidLoad];
[self.view addGestureRecognizer: [[UITapGestureRecognizer alloc] initWithTarget: self action: #selector( dismiss:) ]];
_textField.inputAccessoryView = [[UIView alloc] init];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardWillShowHide:)
name: UIKeyboardWillShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardWillShowHide:)
name: UIKeyboardWillHideNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardDidShowHide:)
name: UIKeyboardDidShowNotification
object: nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardDidShowHide:)
name: UIKeyboardDidHideNotification
object: nil];
}
- (void) keyboardWillShowHide: (NSNotification*) n
{
_displayLink = [CADisplayLink displayLinkWithTarget: self selector: #selector( tick: )];
[_displayLink addToRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
}
- (void) keyboardDidShowHide: (NSNotification*) n
{
[_displayLink removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
}
- (void) tick: (CADisplayLink*) dl
{
CGRect r = [_textField.inputAccessoryView.superview.layer.presentationLayer frame];
r = [self.view convertRect: r fromView: _textField.inputAccessoryView.superview.superview];
CGFloat fromBottom = self.view.bounds.size.height - r.origin.y;
_toolbarBottomDistanceConstraint.constant = fromBottom;
}
- (IBAction) dismiss: (id) sender
{
[self.view endEditing: YES];
}
#end
Here's the view hierarchy and constraints:

I don't have a detailed solution as Tom provided, but I do have an idea you could play around with. I've been doing a lot of interesting things with auto layout and constraints, and you can do some amazing things. Note that you cannot constrain items in a scroll view with things that are in one.
So you have your primary view, I assume its a table view or some other view inside a scrollView, so you have to deal with that. The way I suggest is to take a snapshot of the view, save the current view in an ivar (your table), and replace it with a "very tall container view" that is anchored on the bottom, put the UIImageView containing the snapshot into this view, with a constraint between it and the container view of constant=0. To the user nothing changed.
In the "inputAccessoryView", when the view is added to the superView (and when there is a window property), you can remove the constraint between the image and the container view, and add a new one that constrains the bottom of the text field to the top of your inputAccessoryView, where the distance has to be greater than some value. You have to play around to get the value, as it will be the offset of that textField in your scrollView adjusted for any contentValue. Then, you will most likely have to add that constraint to the window (keeping an ivar to it so you can remove it later).
In the past I played around with the keyboard, and you can see that it gets added to the window, with its frame offset so its just below the bottom of the screen (was in iOS5) - it was not in a scrollView.
When the keyboard finishes scrolling, you can see where the image view has scrolled, determine the offset, then do the switch back from the image view to your real scrollview.
Note that I did do this snapshot, animate, finally replace views in the past quite successfully. You will spend some time on this, and maybe it will work and maybe not - but if you throw together a simple demo project you can verify quickly if you can get an imageView itself in a container view to move using constraints on the keyboard input accessory view. Once that works you can do it "for real".
EDIT: As Tom Swift has pointed out, the keyboard is located in another window at a higher "Z" level, and thus there is no way to directly connect a constraint between the real keyboard and a user view.
However - the in the keyboard notifications, we can get the size of it, the animation duration, even the animation curve. So, when you get the first keyboard notification, create a new transparent view and place it so its top is at the bottom of your special "imageView" (snapshot) view. Use a UIView animation of the length and curve of the keyboard, and your transparent view will animate exactly as the keyboard is animation - but in your window. Place the constraints on the transparent view, and you should be able to achieve the exact behavior you want (in iOS6). Really, supporting iOS 5 at this point - for the 10 people who haven't upgraded yet?!?!?.
If you have to support iOS5, and you want this "bump" behavior, then compute when the animation will reach a size where it "hits" your textField, and when the keyboard starts moving use the UIView animation with delay, so that it doesn't start moving right away, but when it does move, it tracks the keyboard.

Related

inputAccessoryView with custom view hide After I Dismiss keyboard

First of all , my project is so structured:
-home page with a tableView to add element
-floating Button: when clicked make appear keyboard with above a custom view (and inside a textView) to digit input
Now, I have a problem with inputAccessoryView and dismiss keyboard:
I have used inputAccessoryView to move a custom view above the keyboard like this:
MytextView.inputAccessoryView= MyContainerView;
And this work correctly.
My problem occur when I dismiss keyboard using:
[MytextView resignFirstResponder];
The keyboard and relative inputView disappear properly but After when I try again to make MytextView the firstResponder does not work (the keyboard not appear).
I hypothesize that occurs because textView is hide with inputAccessoryView under the screen and inputAccessoryView change The inizial position of textview (Initial in the Middle of the screen); so textView is not focusable and keyboard not appear when I use:
[MyTextView becomeFirstResponder]
Is there a way to reposition programmatically the textView to initial position (middle of the screen) so can become focusable when I call becomeFirstResponder?
Or is there a way to fix inputAccessoryView in the safe area when i dismiss keyboard?
(Sorry , I’m New to objective-c and IOS)
Thanks!
Let me jump out of the comments to have more freedom. Based on your description you could do something like this
- (void) viewDidLoad
{
super.viewDidLoad;
[NSNotificationCenter.defaultCenter addObserver:self
selector:#selector( keyboardDidHide: )
name:UIKeyboardDidHideNotification
object:nil];
}
- ( void ) keyboardDidHide:( NSNotification * ) notification
{
... do stuff here...
}
Then you can reposition or do whatever you need in there. Alternatively, you can also listen for UIKeyboardDidShowNotification and do preparatory stuff there. In fact, there is a whole family of UIKeyboard... notifications that you could use this way and I hope it helps.
EDIT
Here is something else, if you ever need to reposition based on the keyboard size.
- ( void ) keyboardDidShow:(NSNotification *)notification
{
NSDictionary * info = notification.userInfo;
CGRect kbRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
... do stuff with kbRect ...
}
have you tried to use
[self.view endEditing:YES]
to dismiss the input view?
I never get any issue to set any input to firstResponder after I trigger that code.

How do I disable the keyboard without hiding it in iOS?

I have a screen with various elements, including a UITextField, and I want to show coach marks on the screen (I'm using the Instructions library). However, I need to freeze interactions with the elements when the coach marks are displayed (so the user can't type stuff in the text field, for example).
My problem is with the keyboard (which is always displayed in this screen): if I just set the text field's isEnabled property to false, or set isUserInteractionEnabled = false, the field resigns first responder and the keyboard disappears, which is unnatural for this screen. If, however, I use the textField:shouldChangeCharactersIn range: delegate method and return false, the keyboard remains visible, but enabled. So when the coach marks appear, the user can tap around the keyboard. While this has no effect on the text field, it's still weird and annoying.
So how do I temporarily keep the keyboard up, but have it disabled (preferably behind the coach marks blur view)? I saw this, but that doesn't provide a solution that I think Apple would accept.
Thanks,
Yariv.
You can add a UIButton dynamically when the keyboard is visible. You can add keyboardDidShow & keyboardDidHide observer to know when the keyboard is up and visible.
You might face a problem related to keyboard being top most on screen but, you can deal with it by making the newly added UIButton the top most on your current view/window.
Don't forget to set userInteractionEnabled to false for that UIButton
Comment to your question by #MikeAlter would be the best (and clean) solution. But, that also means that you don't have any control with navigating/interacting to other controls on your screen.
EDIT
Keyboard has specific region on screen covered when shown. My solution suggested that you can add a UIButton on your view with the exact rectangle/frame same as of keyboard. A view/button that covers the keyboard overall. So, your keyboard will not take any user interaction.
The following code can get you size of keyboard. You can add a UIButton of same size in exactly same place which will make your keyboard inactive as user interaction will be taken care by the button itself.
- (void)keyboardDidShow:(NSNotification *)notification {
CGSize keyboardSize = [[[notification userInfo] objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
int height = MIN(keyboardSize.height,keyboardSize.width);
int width = MAX(keyboardSize.height,keyboardSize.width);
... Add UIButton here with the CGRect of keyboard
}
You need to register to UIKeyboardDidShowNotification for above to work, like following:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification
object:nil];
If you want to make keyboard behind coach marks blur view then add coach marks blur view to last window:
UIApplication.shared.windows.last?.addSubview(coachMarksBlurView)
UIApplication.shared.keyWindow?.bringSubview(toFront: coachMarksBlurView)
And if you want to block keyboard you can use these two functions:
func addBlockView(){
view.addSubview(viewBlock)
viewBlock.frame = CGRect.init(x: 0, y: 0, w:
UIScreen.main.bounds.size.width, h:
UIScreen.main.bounds.size.height)
viewBlock.backgroundColor = UIColor.clear
UIApplication.shared.windows.last?.addSubview(viewBlock)
UIApplication.shared.keyWindow?.bringSubview(toFront: viewBlock)
}
func removeBlockView(){
viewBlock.removeFromSuperview()
}

How can I assign a pointer to the keyboard before I have assigned first responder

I am trying to create a user interface enabling users to switch between the keyboard and other menus when using a chat application.
On a click of the textField bar I want to raise either the keyboard or a collection view.
The problem occurs when I click the 'menu' button. I want the textField bar to raise revealing my menu view. Then, on a click on the keyboard button, instantly switch to the keyboard, rather than having it slide up from the bottom. This means I need to have the keyboard already loaded and hidden but in the background of the app.
Currently though the earliest I am managing to assign a variable to the keyboard is in the keyboardDidShow function.
-(void) keyboardDidShow: (NSNotification *) notification {
// Get the window the keyboard is a subview of
_window = [UIApplication sharedApplication].windows.lastObject;
_keyboard = _window.subviews[0];
}
This means that after it has been loaded once I can hide and reveal it, but I don't want it visible when it is loading this first time.
To achieve this using alternate means I have tried adding my extra views as subviews of the UIWindow the keyboard is created in:
[_window addSubview:_menuView];
[_window addSubview:_gamesView];
[_window addSubview:_stickerView];
[self hideSpecificView];
Unfortunately I keep coming across the same problem, until I have loaded the keyboard once it needs to fully load before I can get a pointer to it to hide it.
Here is a picture of my toolBar incase I am not being clear:
On clicking the menu icon or the stickers icon I want the bar to raise with a collection view. If I then click the textfield, with these views visible, I want to hide the visible view to immediately show the keyboard behind.
I have also tried experimenting with keyboardWillShow but as the window hasn't been loaded in front our screen I can't get a pointer to the keyboard to hide it before it loads.
An example of what I am after can be found many chat apps (facebook messenger, LINE, Kakao Talk)
Any help would be greatly appreciated
Although the way I came up with isn't perfect it works almost perfectly so hopefully this might help people in the future. If anyone else has solved it differently please post as it would be interesting to know how you did it.
I started by adding a class variable to a UIWindow in my header file and then setting off a timer to ping just after the keyboard will show method finishes. After this method has finished the keyboard has been created, just, and so I allocate it and hide it.
-(void) keyboardWillShow: (NSNotification *) notification {
// More keyboard code
_window = [UIApplication sharedApplication].windows.lastObject;
[NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(allocateKeyboard)
userInfo:nil
repeats:NO];
}
- (void)allocateKeyboard {
if (!_keyboard) {
_keyboard = _window.subviews[0];
}
_keyboard.hidden = YES;
[self setViewForButtonType];
}
I have already previously added my other views, hidden them and constrained them to the bottom of the main view, this means that when the keyboard rises they do too.
- (void)viewDidLoad {
[self.view addSubview:_menuView];
[self.view addSubview:_gamesView];
[self.view addSubview:_stickerView];
}
...
- (void)hideViews {
_keyboard.hidden = YES;
_menuView.hidden = YES;
_gamesView.hidden = YES;
_stickerView.hidden = YES;
}
When buttons get pressed I simple then unhide the view that I want to see and hide the rest of the views.
When I say that this method doesn't work perfectly it is because if you load view main view and then click a button before the keyboard has loaded for the first time then you get a quick glimpse of the keyboard before the view appears over the top. This though only happens the first time and only if they don't click in the text field first.
Anyway, I found this was the best way of making views look like they are in front of the keyboard. Obviously my code was a lot longer and more complex (too long for here) but this is the gist of the method I used to solve it. Comment if you have any queries and I hope this helps.

Make UITextView parent be its own inputAccessoryView

I'm trying to achieve a similar keyboard interaction that Messages has in iOS 7. I have a UIView which contains a UITextView, and when the user selects it to start typing, I want to make this UIView the inputAccessoryView. This would take care of the animation for me, as well as the new UIScrollView keyboard dismiss interaction in iOS 7.
When the UITextView begins editing, I'm trying to set its inputAccessoryView to its parent UIView (which is already in the view hierarchy). The keyboard appears but not with an accessory view.
I've read some people are using a duo of UITextFields to make this work, but that seems like a bad way to achieve this.
Any suggestions?
A much easier solution is to make your input field the input accessory view of your view controller:
- (BOOL)canBecomeFirstResponder
{
return YES;
}
- (UIView *)inputAccessoryView
{
return self.yourInputField;
}
The view will be on screen at the bottom of the screen and when it becomes first responder in response to a user tapping it, the keyboard will be presented. The view will be animated such that it remains immediately above the keyboard.
The only way to get this to work is via a second text field. The idea is to make it a subview but not visible (due to crazy rect). You then switch firstResponder back and forth between it and the real text field while its getting delegate methods. I created a some one viewController test project and did this (you can copy paste and verify behavior with about 2 minutes of time):
#implementation ViewController
{
UITextField *field;
UITextField *dummyView;
}
- (void)viewDidLoad
{
[super viewDidLoad];
field = [[UITextField alloc] initWithFrame:CGRectMake(0, 460, 320, 20)];
field.borderStyle = UITextBorderStyleRoundedRect;
field.delegate = self;
//field.inputAccessoryView = field;
field.text = #"FOO";
[self.view addSubview:field];
dummyView = [[UITextField alloc] initWithFrame:CGRectMake(0, 40000, 320, 20)];
dummyView.delegate = self;
[self.view addSubview:dummyView];
}
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
if(textField == field && textField.superview == self.view) {
[field removeFromSuperview];
dummyView.inputAccessoryView = field;
[dummyView becomeFirstResponder];
}
return YES;
}
#end
I should add I've used this technique in shipping apps since iOS 4.
EDIT: So a couple of other ideas:
1) To make the glitch when the keyboard starts moving look a little better, you could take a snapshot of your textView, put that into a UIImageView, and when you remove the textView from the primary view, replace it with the UIImageView. Now the appearance is the same. Add an animation for the image so that noting happens for 50 ms, then the alpha goes to 0. Add a similar animation to your real textview, so that it has an alpha of 0 for 50 ms, then it goes to 1. You may be able to tweak this so the transition is good (but not great).
2) The way apple probably does this is to get the animation curve and timing from the keyboard moving notification. In this case they would add a accessory view with 0 height at first, and animate the textField so its tracking the keyboard, but above it. Both moving same distance at the same time. At the end of the animation, the textField is pulled out of self.view, the accessory view has its frame changed to have the height of the textField, and the textField is placed as a subview of the accessory container view. This should work but yeah, its a bit complex to do. If you want someone to code it for you offer a 100 pt bounty. You still need the dummy text field for when you go and move the textField at the end, since when you take it out of its containing view it will resign first responder. So at the end, you make the dummy field the first responder, move the textfield, then make the real textfield the first responder again.
This actually works best if you don't use .inputAccessoryView at all and instead just animate the position of the parent UIView as the keyboard opens and closes. Here is an answer describing the process step-by-step with all the code.

How to make to keyboard has behavior like in default apps when text field is inside scroll view?

I have created in storyboard simple app (only one controller), I put scrollview and inside scrollview couple UITextFileds. Inside controller I have added function like
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.name resignFirstResponder];
[self.number resignFirstResponder];
// I have tried with and without this line but doesn't work
[self.scrollView resignFirstResponder];
}
(name, number are Outlets of UITextField, scrollView is Outlet of UIScrollView). When I click on any of those text fields keyboard pops up but when I finish typing I cannot hide keyboard.
(In previous version I didn't have scrollview and keyboard hides when I click out the text field). How to make to keyboard has behavior like in default apps, how to hide ?
I'm assuming you want to just be able to tap away from the keyboard and have it dismissed right? Just do this:
UITapGestureRecognizer *myTapz = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(userTapped)];
myTapz.numberOfTapsRequired=1;
myTapz.cancelsTouchesInView=NO;
[self.view addGestureRecognizer:myTapz];//or do self.WhateverYourOtherViewIsCalled..tableview? scrollView?
[myTapz release];
And then in your selector:
-(IBAction)userTapped
{
[whateverYourTextFieldIsCalled resignFirstResponder];
}
In your view controller:
[self.view endEditing:YES];
This will dismiss the keyboard no matter what field is the first responder. I think there are some exceptions, but for what you're doing it should work fine.
Also touchesBegan is a UIView method, not a UIViewController method. If you're putting it inside your UIScrollView, the scroll view's panGestureRecognizer is going to prevent touchesBegan from being called. Also when overriding touchesBegan, or other touches methods, you typically want to call super as well.
ttarules's suggestion for creating a gesture recognizer is the best way for detecting touches. You can use touchesBegan inside the view, just know that other gesture recognizers can prevent it from being called (see Session 121 - Advanced Gesture Recognition from WWDC 2010).
endEditing is the best way to dismiss the keyboard because it works even after you add other fields.

Resources