20

I'm looking for a way to slide the keyboard into view from the right, like what happens in the Contacts application when you edit a note.

My problem is that when I call [someTextView becomeFirstResponder] in viewWillAppear, the keyboard immediatly pops up with no animation. And when I call it in viewDidAppear, the view first slides in from the right (UINavigationController does the sliding), and then the keyboard slides in from the bottom.

Is it possible to have the keyboard slide in from the right, together with the view?

T .
  • 4,874
  • 3
  • 23
  • 36
  • I don't have an answer for you, but I noticed today that AIM does the same thing as Contacts. So presumably it's possible. – Steven Fisher Jul 16 '09 at 23:22
  • I had the same problem and managed to find this answer: [here](http://stackoverflow.com/questions/2658261/uitextfield-subview-of-uitableviewcell-to-become-first-responder) – Daniel Granger Apr 22 '10 at 14:44

5 Answers5

17

Solution

In iOS 7, calling becomeFirstResponder on _textView in viewDidLayoutSubviews works.

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    [_textView becomeFirstResponder];
}

Note: Doing it in viewWillLayoutSubviews also works.

Explanation

Read the discussion in the docs for becomeFirstResponder.

You may call this method to make a responder object such as a view the first responder. However, you should only call it on that view if it is part of a view hierarchy. If the view’s window property holds a UIWindow object, it has been installed in a view hierarchy; if it returns nil, the view is detached from any hierarchy.

When using a navigation controller to push your custom view controller onscreen, self.view.window is still nil by the time either viewDidLoad or viewWillAppear: is called. So, _textView.window is also nil in the same methods, since _textView is a subview of self.view, i.e., they're both in the same window. No matter how you present your custom view controller, self.view.window (and thus _textView.window) is also nil in initWithNibName:bundle:. self.view.window is set by the time viewDidAppear: is called, but that's too late because by that time, the navigation controller has already completed the animation of pushing the view onscreen.

self.view.window is also set by the time either viewWillLayoutSubviews or viewDidLayoutSubviews is called and these methods are called before the push animation of the navigation controller begins. So, that's why it works when you do it in either of those methods.

Unfortunately, viewWillLayoutSubviews and viewDidLayoutSubviews get called a lot more than just on the initial navigation controller push. But, navigationController:willShowViewController: and willMoveToParentViewController: get called too soon (after viewDidLoad but before self.view.window is set) and navigationController:didShowViewController: and didMoveToParentViewController: get called too late (after the push animation).

The only other way I can think of doing it is to somehow observe the window property of _textView so that you get notified when it changes, but I'm not sure how to do that since window is readonly.

ma11hew28
  • 121,420
  • 116
  • 450
  • 651
  • I have been looking for a solution for this for a long time. Is this new in iOS 7 or have I been missing it? Great solution! – SAHM Nov 17 '13 at 13:55
  • 1
    It works, but you need to add some mechanism to allow the only one-time execution of this code because *viewWillLayoutSubviews* or *viewDidLayoutSubviews* called to often while interacting with UI. – malex Dec 17 '13 at 16:15
  • Also you can see the side effect while moving. The motion rate of the pushing view controller is a bit faster than motion of keyboard (especially at the first time). The keyboard tries to catch up left boarder of the animating bounding view. – malex Dec 17 '13 at 16:19
  • 2
    I'm noticing that there's a significant delay the first time the view is loaded when using this method. Any idea on how to resolve that? – stewart715 Feb 18 '14 at 22:14
  • Preload the keyboard to remove the initial delay: https://github.com/mbrandonw/UIResponder-KeyboardCache – stewart715 Feb 18 '14 at 23:31
  • Thank you so much for making it clear to me what the deeper issue was :) – Stian Høiland Sep 30 '14 at 09:06
11

All you need to do is tell the text view in question to become the first responder in the viewDidLoad method of the view controller you're pushing onto the navigation stack:

override func viewDidLoad() {
    super.viewDidLoad()
    someTextView.becomeFirstResponder()
}

This works in iOS 8. The keyboard slides in from the right along with the view.

ma11hew28
  • 121,420
  • 116
  • 450
  • 651
titaniumdecoy
  • 18,900
  • 17
  • 96
  • 133
  • I have also written a small test using this solution and it works exactly as the Contacts app does when editing a note. +1! – Jasarien Jul 18 '09 at 21:57
  • This is the correct answer, although not entirely for my specific case (it put me on the right track though). My text view was inside a table single view cell (due to me wanting the rounded border and being lazy). Apparently, the keyboard doesn't like this. It turned out to be the cause of the keyboard instantly appearing rather than sliding in from the right. Your answer helped me track down the cause of my problem, so thank you! – T . Jul 19 '09 at 01:03
  • 5
    This solution isn't working for me in iOS 7. Has anyone found a solution that works in iOS 7? – Alex Pretzlav Oct 01 '13 at 16:57
  • 2
    @AlexPretzlav I found a [solution that works in iOS 7](http://stackoverflow.com/a/19580888/242933). – ma11hew28 Oct 25 '13 at 04:03
  • [The universal solution (for iOS 7 also)](http://stackoverflow.com/a/20639142/2066428) – malex Dec 17 '13 at 16:22
  • 1
    doesn't work for me that way... The keyboard is just instantly there. Also notes edit in contacts doesn't push but edit inline?!? I am running on iOS8... – Georg Jun 08 '15 at 09:52
  • It's works about 2/3rds of the time in iOS 9 + actual device (always works in simulator) + brand new project. I've also tried calling `becomeFirstResponder` in `viewWillAppear`, `viewDidLayoutSubviews`, and even tried before the viewController was pushed onto the stack. All instances only work about 2/3 of the time on an actual device. – Vadoff Aug 09 '16 at 23:11
  • @Vadoff Are you using a 3rd party keyboard? Those can take a long time to load (up to multiple seconds on iOS 9) and that may be why it's not working for you -- the view slides in before the keyboard finishes loading. – titaniumdecoy Aug 09 '16 at 23:14
  • Default keyboard + brand new project. – Vadoff Aug 09 '16 at 23:30
3

In iOS 7 (or any version before) you can make a simple thing in loadView, viewDidLoad or viewWillAppear

[yourTextView performSelector:@selector(becomeFirstResponder) withObject:nil afterDelay:0.0];

In this case you will get left-to-right appearance of the keyboard aligned with the motion of pushing view controller.

malex
  • 9,874
  • 3
  • 56
  • 77
  • This only works efficiently on iOS simulator. On an actual device it's a bit buggy. – stewart715 Feb 18 '14 at 23:09
  • 1
    This worked great for me even on device. I was running into problems with the subview layout hooks mentioned above. Why? I had subviews causing a re-layout that would force the textfield to become first responder again. Ouch. This solution had a pleasant slide effect too. Thanks! – drudru Aug 03 '14 at 23:24
3

For iOS 7 I've found the following solution to work the best for me:

-Import UIResponder-KeyboardCache to your project.

-Add [UIResponder cacheKeyboard:YES]; to the viewDidLoad of the view before the keyboard view. It might be better to do this immediately when the application loads or during a time convenient when you can afford it (during an HTTP request, for example). In most cases, simply in the view before is sufficient.

-Add the following to the viewDidLoad of the keyboard view.

dispatch_async(dispatch_get_main_queue(), ^{
  [_textField becomeFirstResponder];
});

To explain, this will preload the keyboard view, which will remove the delay from the first call of the keyboard view. Calling becomeFirstResponder on the text field in the main queue causes it to slide in with the view instead of animating upward before the view slides in.

stewart715
  • 5,557
  • 11
  • 47
  • 80
  • This rocks. Thanks a lot! Easiest solution to this issue, I think your category will be in my every project. Thanks again! – p4sh4 Jul 05 '14 at 13:15
0

You could try sending the becomeFirstResponder message to the new view controller before you push it onto the stack. For example:

-(void)functionWhereYouPushTheNewViewController {
  yourNewViewController *newVC = [[yourNewViewController alloc] init];
  [newVC.yourTextView becomeFirstResponder];
  [self.navigationController pushViewController:newVC animated:YES];
}

I have found that changing animations on things like they keyboard is pretty tough though, and if you read the Human Interface Guidelines Apple makes it pretty clear that they want certain things to act in certain ways, all the time. There are ways to change the behaviors of certain animations but they often involve undocumented API calls and are grounds for rejection from the app store. It would be a violation of HIG to have pushed views slide up from the bottom, for example.

Hope this helps.

Tim Bowen
  • 677
  • 6
  • 17
  • Oddly enough, if I call becomeFirstResponder before pushViewController it does nothing. Perhaps because the view hasn't actually been made part of the interface yet. Calling it after pushViewController causes the keyboard to just pop up instantly. If getting the keyboard to behave is as hard as you say (although Apple does it somehow in the Contacts app), then perhaps I should just leave it be. – T . Jul 16 '09 at 00:39