Swift: Enable "dismiss keyboard" button on iPhone? - ios

I see numerous posts (e.g. "hide keyboard for text field in swift programming language") about how to make the keyboard disappear after hitting the return button, or by clicking outside the UITextView (and subsequently resigning first responder, and/or setting endEditing to true). This is not what I want to do; I neither want to use the return key -- I want the user to be able to use the return key for actually adding a newline to the text they're entering in the UITextView -- nor force the user to click outside in order to dismiss the keyboard.
On an iPad there is a "dismiss keyboard" button which naturally appears, as part of the keyboard itself, that dismisses the keyboard. It's usually in the lower right of the keyboard (below the right shift key), and it's a little picture of a keyboard with a down arrow on it.
How does one enable this button on an iPhone, i.e. to have the keyboard which appears also include the dismiss button?
This post: "dismiss iphone UITextView Keyboard" seems to advocate adding a toolbar to the top of the keyboard. Is there not a way to simply select an optional keyboard that includes a dismiss-key option, similar to the way one can provide a keyboard with "#" on the main keyboard screen when an email address is expected?
Thanks.
PS- For clarity: I do not want to set the return key to "Done" (as per this post), I want to keep it as a return key. I'm willing to add an extra UIButton if that's the only way, but it seems like there should be a keyboard option which includes a dismiss button already, in addition to the return key.

I'm sad to say that the correct answer to your request is that there is no possible way to do what you want.
As you have garnered from your research, the community has found lots of work arounds, but that's all we have.

add a notification, which will be triggered when keyboard is about to show
Then custom a view, here you can change it to your button
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(changeContentViewPoint:) name:UIKeyboardWillShowNotification object:nil];
- (void) changeContentViewPoint:(NSNotification *)notification{
NSDictionary *userInfo = [notification userInfo];
NSValue *value = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
CGFloat keyBoardEndY = value.CGRectValue.origin.y; // get the y of the keyboard
NSNumber *duration = [userInfo objectForKey:UIKeyboardAnimationDurationUserInfoKey];
NSNumber *curve = [userInfo objectForKey:UIKeyboardAnimationCurveUserInfoKey];
// add animation, make your view move as the keyboard moves
[UIView animateWithDuration:duration.doubleValue animations:^{
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationCurve:[curve intValue]];
_mainView.center = CGPointMake(_mainView.center.x, keyBoardEndY - STATUS_BAR_HEIGHT - _mainView.bounds.size.height/2.0); // keyBoardEndY including the height of the status bar, so get rid of this height.
}];
}

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.

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 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.

Animate UIView along keyboard appear animation

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.

Resources