mutualmobile / MMDrawerController

A lightweight, easy to use, Side Drawer Navigation Controller
MIT License
6.76k stars 1.38k forks source link

Hide keyboard when swiping to drawer #418

Open MartinSchultz opened 9 years ago

MartinSchultz commented 9 years ago

I'm showing a UIViewController in the center view with a text field. Keyboard shows up, but I need to hide it when the user swipes in the left drawer navigation.

I've searched the issues here but the solutions posted didn't work out for me. I tried using viewWillDisappear, but this method is not getting called. Is there any way to hook into the MMDrawer internals to find out when the user swipes in the drawer navigation so I can hide the keyboard?

Actually, the keyboard already disappears when swiping, but it comes back again when the left menu appears.

Thanks a lot Martin

MartinSchultz commented 9 years ago

Anyone?

ilidar commented 8 years ago

@MartinSchultz check this out https://github.com/mutualmobile/MMDrawerController/issues/407#issuecomment-161097378

MartinSchultz commented 8 years ago

I'm not quite getting how this solves the issue. What I see is the keyboard goes down (it was open and active on a text field) and once I swipe in the menu from the left, the keyboard pops up again with the side menu visible. Can you elaborate how your code fixes that? Maybe I'm too blind this morning :-) but I don't get it. (and thanks anyway for helping out!!)

ilidar commented 8 years ago

@MartinSchultz I've thought you could simply detect in setGestureShouldRecognizeTouchBlock: block that you have keyboard visible and change your behavior accordingly (e.g. dismiss it). But maybe it's a hacky way :-(.

NKorotkov commented 8 years ago

There's a way, but it's a bit cumbersome.

@property (nonatomic, assign, readonly) MMDrawerSide openSide;

It's readonly so you cannot override the setter to inject your code anytime openSide changes. But luckily it's KVO-able. So you can do something like following. Subclass mmdrawercontroller and add this method:


static void * kSCRBSCRBDrawerControllerKVOContext                  = &kSCRBSCRBDrawerControllerKVOContext;
static NSString * const SCRBKVOOpenSideKeyPath                     = @"openSide";

NSString * const SCRBDrawerControllerDidChangeOpenSideNotification = @"SCRBDrawerControllerDidChangeOpenSideNotification";
NSString * const SCRBDrawerControllerOpenSideKey                   = @"SCRBDrawerControllerOpenSideKey";

- (void)startSendingOpenSideNotifications {

    [self addObserver:self forKeyPath:SCRBKVOOpenSideKeyPath options:NSKeyValueObservingOptionNew context:kSCRBSCRBDrawerControllerKVOContext];

}

#pragma mark - KVO

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {

    if (context == kSCRBSCRBDrawerControllerKVOContext) {

        MMDrawerSide openSide = [[change valueForKey:NSKeyValueChangeNewKey] integerValue];

        [[NSNotificationCenter defaultCenter] postNotificationName:SCRBDrawerControllerDidChangeOpenSideNotification object:nil userInfo:@{SCRBDrawerControllerOpenSideKey : @(openSide)}];

    } else {

        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 
    }

}

- (void)dealloc {

    [self removeObserver:self forKeyPath:SCRBKVOOpenSideKeyPath context:kSCRBSCRBDrawerControllerKVOContext];

}

All you need is to call startSendingOpenSideNotifications whenever you want, in viewDidLoad for example. After that your drawer controller starts posting app wide notifications every time you try to open the drawer menu. You can create a UITextView\UITextField subclass that will react to these notifications like this:

#import "SCRBDrawerController.h"
#import "SCRBTextField.h"

@implementation SCRBTextField

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self scrb_commonInit];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self scrb_commonInit];
    }
    return self;
}

- (void)scrb_commonInit {

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_handleDrawerSideChange:) name:SCRBDrawerControllerDidChangeOpenSideNotification object:nil];

}

- (void)dealloc {

    [[NSNotificationCenter defaultCenter] removeObserver:self];

}

- (void)_handleDrawerSideChange:(NSNotification *)ntf {

    [self resignFirstResponder];
}

@end
mmahkamov commented 8 years ago

Here's how I do it

Basically, you need to resign the first responder to hide the keyboard. Since you don't know which view is the first responder, you can send the action to the UIApplication like this:

[[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];

Now, there are 2 cases that I know of how the drawer can be opened.

Case 1:

The drawer is opened using a gesture. Set a gesture completion block that resigns the first responder:

[self.drawerController setGestureCompletionBlock:^(MMDrawerController *drawerController, UIGestureRecognizer *gesture) {
            // hide the keyboard when the gesture completes
            if(drawerController.openSide == MMDrawerSideLeft) {
                [[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
            }
        }];

Case 2:

The drawer is opened by toggling its state (e.g. when a "menu" button is touched). Resign the first responder when it finishes:

- (void)leftDrawerButtonPress:(id)sender {
    [self.mm_drawerController toggleDrawerSide:MMDrawerSideLeft animated:YES completion:^(BOOL finished) {
        // hide the keyboard when the drawer animation completes
        if(finished) {
            [[UIApplication sharedApplication] sendAction:@selector(resignFirstResponder) to:nil from:nil forEvent:nil];
        }
    }];
}

I have only a left drawer, hence I'm handling only MMDrawerSideLeft. You can change that to your liking.

I hope this helps.