Make UITextView parent be its own inputAccessoryView - ios

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.

Related

UIView stuck behind UITableview header

In my app I have a UITableViewCell with a UITextField in it. When the user starts typing in this textfield, an autocomplete view appears.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
if (self.suggestions != nil) {
self.autocomplete = [[AutoComplete alloc] initWithFrame:CGRectMake(10, 53, self.frame.size.width-20, 200)];
self.autocomplete.delegate = self;
self.autocomplete.suggestions = self.suggestions;
[self addSubview:self.autocomplete];
[self bringSubviewToFront:self.autocomplete];
}
return YES;
}
This is nothing more than a UIView with a UITableView inside it. However, what happens is that this view is hidden behind the section header and the next cell.
So while it appears to be inserted above the cell, it registers the tapp below it. When you click in the autocomplete it registers the click in the next cell. How can I fix this?
Your issue is you are adding it to the view and not the tableview which resides in the UIView as well as having a section header that will probably block it out as well. I see your y position is only 53, so your header is likely blocking it out.
I am not 100% sure where you want your result to be viewed and used, that's not clear in your question. If you want your view to be above everything else you could:
"I have a UITableViewCell with a UITextField in it. When the user starts typing in this textfield, an autocomplete view appears." - add the result view to your cell, not the main view; [cell.contentView addSubview...
Shift your tableview down by changing it's y position to self.autocomplete.view.frame.size.height. Add a nice little animation to it as it changes position too, always something that bit extra.
Consider adding it as a subview to your TableView and bringing to front, should work.
(little bit extra on the cell.contentView)

Not properly add inputAccessoryView for UITextField

There is a requirement of inputAccessoryView is for the chat application.
When I add inputAccessoryView to UITextField on tap event of UIButton. That view is set as inputAccessoryView completely but text field is not becoming first responder.
(Note: Super view of textfield is viewText.)
- (IBAction)btnOpenTextField:(id)sender
{
UIView *accessoryView=[[UIView alloc]init];
accessoryView.frame=CGRectMake(0,0, _viewText.frame.size.width, _viewText.frame.size.height);
[accessoryView addSubview:_viewText];
_txtMessage.inputAccessoryView = accessoryView;
[_txtMessage becomeFirstResponder];
}
Thanks in Advance.
You can call [_textMessage reloadInputViews], replacing [_text becomeFirstResponder].
What you want to achieve is quite not possible, this will cause recursion, alternatively what you can do is put a textfield on bottom of the screen and when the textfield is selected you can animate it up with the flow of the keyboard. i think this is an appropriate solution if i got your question correctly.

UITextField frozen with no keyboard

Can anyone think of a reason that a UITextField would resignFirstResponder, and then no longer respond to touches to becomeFirstResponder? I have a situation where I have a view controller that appears, but the keyboard disappears immediately, and the text field still has the cursor, but it's not blinking. Tapping on the text field will cause the "paste" menu option to appear and I can paste text into the field, but I cannot make it become first responder.
Any thoughts would be greatly appreciated...
I've had to deliberately create this scenario before for a previous app and the way i did it was to set the input view for the textfield to a tiny dummy view.
UIView* view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 1)];
myTextField.inputView = view;
Although I believe this still showed the blinking cursor.
To get the blinking cursor to stop as well I had to return NO in the shouldBeginEditing method.
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
return NO;
}
So if you are doing either of these things you would get the behaviour that you're currently experiencing where the textfield is actually responding but it is isn't showing a keyboard or a blinking cursor.

How to disable keyboard when clicking a UITextField in iOS?

I am currently developing a prototype that I want to do user testing on desktop first before loading to iPad.
I am looking for solutions to disable the keyboard after clicking a textfield. That means after clicking a textfield, user is able to enter information from the macbook keyboard directly, and the virtual keyboard that automatically shows up in the simulator will not appear. I have been through a lot of tutorials but they are all dismissing the keyboard after user entry; that is not what I am looking for. How should I hide the keyboard?
Thanks so much!
Use this:
UIView *dummyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
myTextField.inputView = dummyView; // Hide keyboard, but show blinking cursor
It works for UITextField and UITextView and they need to be set to editable.
What you did Here:
You created a dummy view of width=hight=0, & assigned it as the inputView of your textField.
How It works:
Instead of showing default, keyboard, now, the viewController is showing DummyView as inputView for your UITextField. As DummyView has Width=height=0, You will not see anything on the screen :)
Here is another answer which I found the same hack but with little additional supportive code snippet to hide the blinking cursor too.
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
return NO; // Hides both keyboard and blinking cursor.
}
I needed this to be done for a Quantity text field where I increase/decrease the quantity using a UIStepper view. So I needed the keyboard to be hidden always.
This will set the inputView of your textField to, basically, an empty UIView with no frame.
self.theTextField.inputView = [[UIView alloc] initWithFrame:CGRectZero];

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