26

I'd like to get the behavior similar to Messages app (also common in most texting apps) in iOS7, where in a conversation view swiping right from the left edge of the screen would behave like the back button in a UINavigationController.

I have managed to implement this behavior, however, if the keyboard is open in the presenting view, when I start swiping back, the keyboard gets stuck and does not animate with the view to the right as I move my finger. I'd like to animate keyboard and the presenting view as one unit, not as if keyboard is on top of the other views and they are animating behind it, which is what I get now (see the second screenshot):

(UPDATE: Note that the keyboard will eventually go away after the main view animation is finished; what I am focused on is the position of keyboard during the swipe process, and when you keep touching the device half of the way, which is not in sync with the actual view. The second screenshot clarifies this desired behavior. I also wonder why it is not the default.)

It is easy to replicate the issue by simply creating a new master-detail iPhone app in Xcode 5.0.2 and adding a Text Field to the detail view (preferably somewhere in the upper half) in the StoryBoard, running the app, adding an item, tapping on it to go to the detail view and clicking on the text field you added. Edge-swipe from the left side of the device while keeping your finger on it and you'll see the issue.

Desired behavior:

Desired behavior

Current behavior:

Keyboard tearing

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • Please post the code you’re using to set up the edge-swipe gesture behavior. A UINavigationController should take care of the keyboard for you, but if you’re doing something custom then you may be stuck with this behavior. – Noah Witherspoon Jan 08 '14 at 00:22
  • 2
    @NoahWitherspoon Thanks for your comment. I tried this again to isolate the issue by creating a new project from scratch without writing any code: you can replicate this by simply creating a new Master-Detail iPhone app and simply dropping a Text Field onto the detail view. Run the app, add an item in the master view, click on it to go to the detail view. Tap on the text field and try the edge-swipe gesture. It'll keep the keyboard on top. – Mehrdad Afshari Jan 08 '14 at 02:03
  • Did you solve the problem?! – kokos8998 Apr 05 '17 at 08:56

3 Answers3

15

Unfortunately, there is no built-in method to do that. I really hope there will be something like UIScrollViewKeyboardDismissModeInteractive for UIViewControllers.

For now, to do any animations in-between viewControllers, you should use a transitionCoordinator:

- (BOOL)animateAlongsideTransition:(void (^)(id <UIViewControllerTransitionCoordinatorContext>context))animation
                        completion:(void (^)(id <UIViewControllerTransitionCoordinatorContext>context))completion;

- (BOOL)animateAlongsideTransitionInView:(UIView *)view
                               animation:(void (^)(id <UIViewControllerTransitionCoordinatorContext>context))animation
                              completion:(void (^)(id <UIViewControllerTransitionCoordinatorContext>context))completion;

For the keyboard you should do something like this:

[self.transitionCoordinator animateAlongsideTransitionInView:self.keyboardSuperview
                                                   animation:
^(id<UIViewControllerTransitionCoordinatorContext> context) {
    self.keyboardSuperview.x = self.view.width;
}
                                                  completion:nil];

As for keyboardSuperview - you can get that by creating a fake inputAccessoryView:

self.textField.inputAccessoryView = [[UIView alloc] init];

Then the superview will be self.textField.inputAccessoryView.superview

monder
  • 379
  • 3
  • 7
  • Thanks for your answer. I had thought about things along this line and I tried making it work again after you brought it up. Problem is, in iOS 7, I do not seem to be able to get access to superview of the dummy `inputAccessoryView` to reach the keyboard. It seems to always be `nil`. – Mehrdad Afshari Jan 10 '14 at 23:55
  • It may be nil when the keyboard is hidden. If you don't want to mess with keyboard notifications, you can add an `inputAccessoryView` in `viewDidLoad` method, and put all the logic for retrieving it and adding an animation in `viewWillDisappear:` method. – monder Jan 11 '14 at 09:51
  • I finally managed to do this by adding the dummy accessory view in `init` and caught the superview of the dummy keyboard (keyboard view) in `keyboardDidShow` event. In `viewWillDisappear` I initiated the transition. Seems like this was the magic combination and any deviation from it failed. Thanks! – Mehrdad Afshari Jan 12 '14 at 02:43
  • Where should the `animateAlongsideTransition:completion:` method go? I'm not super familiar with the new UIViewController interactive transitions and the transitionCoordinator, so any help you could give would be awesome. – Matt Mar 05 '14 at 07:45
  • 1
    @Matt `viewWillDisappear:`. Also I've added this code to `beginAppearanceTransition:animated:` and it has worked for me. – derpoliuk Mar 21 '14 at 12:01
  • It works for viewControllers `release`d after pushing, but in my case I am using shared instances of VCs (never released) and the keyboard does not re-appear on re-pushing the shared instance of the VC. How do I make the keyboard behave normally after the transition ends ? – n00bProgrammer Apr 11 '14 at 10:29
  • @n00bProgrammer is there a real need to use shared instance of VC in app? It sounds like a bad design for me. – derpoliuk Apr 15 '14 at 09:49
  • Sharing is not across the board. Each ViewController 'owns' one instance of each child controller, and refreshes the view of the child controller according to user actions. – n00bProgrammer Apr 15 '14 at 09:59
  • @monder. I studied your answer to same problem in another question, and it became clear. Solved it. Thank you. – n00bProgrammer Apr 18 '14 at 09:09
6

It should just work automatically, but sometimes it doesn't because of some conditions.

If the current firstResponder is located inside of active UIViewController and it dismiss throughout UINavigationController mechanism, the expected keyboard animation (horizontal) will be performed automatically. Therefore, sometimes this default behaviour is broken by other strange factors and the keyboard starts to disappear with slide-down animation instead of horizontal animation.

I spent some days with debugging internal UIKit stuff (around methods needDeferredTransition, allowCustomTransition and other) to find one special factor that plays key role in my case.

I discovered that the logic inside UIPeripheralHost checks frame of current UIViewConroller's view, frame of UINavigationController's view (container) and screen size and, if it all doesn’t equal each other, UIPeripheralHost decides that this current situation seems like modal window and sets flag allowCustomTransition = NO. That turn-off UINavigationController-specific horizontal animation.

Fixing issue with frames completely solves my problem.

If you are experiencing same problems, you can try to debug internal UIKit stuff around these private methods and find your conditions that turn off horizontal animation:

https://github.com/JaviSoto/iOS8-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UIPeripheralHost.h

https://github.com/JaviSoto/iOS8-Runtime-Headers/blob/master/Frameworks/UIKit.framework/_UIViewControllerKeyboardAnimationStyle.h

Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
2

You can use https://github.com/cotap/TAPKeyboardPop if you don't need anything special.

In my case I've got some logic connected with UIKeyboardWillShowNotification and UIKeyboardWillHideNotification that were fired on "swipe-to-back" gesture. I've combine this answer and TAPKeyboardPop and this is what I've got:

- (void)beginAppearanceTransition:(BOOL)isAppearing animated:(BOOL)animated {
    [super beginAppearanceTransition:isAppearing animated:animated];
    if (isAppearing || !animated || !_keyboardIsShown) {
        return;
    }
    if ([self respondsToSelector:@selector(transitionCoordinator)]) {
        UIView *keyboardView = self.searchBar.inputAccessoryView.superview;
        [self.searchBar becomeFirstResponder];
        [self.transitionCoordinator animateAlongsideTransitionInView:keyboardView
                                                           animation:^(id<UIViewControllerTransitionCoordinatorContext> context)
         {
             CGRect endFrame = CGRectOffset(keyboardView.frame, CGRectGetWidth(keyboardView.frame), 0);
             keyboardView.frame = endFrame;
         } completion:^(id<UIViewControllerTransitionCoordinatorContext> context)
         {
             if (![context isCancelled]) {
                 [self.searchBar resignFirstResponder];
             }
         }];
    }
}

EDIT:

I've added >iOS7 support and logic for knowing when keyboard is shown (_keyboardIsShown is set in UIKeyboardWillShowNotification/UIKeyboardWillHideNotification or in UIKeyboardDidHideNotification/UIKeyboardDidShowNotification).

Community
  • 1
  • 1
derpoliuk
  • 1,756
  • 2
  • 27
  • 43
  • 1
    Hey. Can you explain in more detail what this code is intended to do? – Stian Høiland Nov 18 '15 at 20:44
  • @StianHøiland don't have code on my hands now (and code in this answer is odd, not sure why `self.searchBar` is there, probably should be text), but as far as I remember - it makes keyboard move with view controller, that's being swiped (first screenshot in question). – derpoliuk Dec 03 '15 at 06:51