ios

一个可以全局使用的键盘监听类-iOS

2020-03-08  本文已影响0人  Dawn_wdf

开发的过程中使用到键盘监听,每个页面都写监听然后再移动页面太繁琐,写了一个类用来全局监听,目前为止基本符合APP开发应用的场景,如果遇到新的问题再更新。代码中使用了ARC,可使用- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;替换。

- (void)configKeyboard {
    
    SKeyboardManager *keyboardManager = [[SKeyboardManager alloc] init];
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    //键盘弹出
    [[notificationCenter rac_addObserverForName:UIKeyboardWillChangeFrameNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        [keyboardManager moveViewsForKeyboardInfo:x];
    }];
   
    [[notificationCenter rac_addObserverForName:UIKeyboardWillHideNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        [keyboardManager moveViewsForKeyboardInfo:x];
    }];
 }
//
//  SKeyboardManager.h
//  Snatch
//
//  Created by DawnWang on 2020/2/9.
//  Copyright © 2020 Dawn Wang. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface SKeyboardManager : NSObject

- (void)moveViewsForKeyboardInfo:(NSNotification *)noti;

@end

NS_ASSUME_NONNULL_END

.m

//
//  SKeyboardManager.m
//  Snatch
//
//  Created by DawnWang on 2020/2/9.
//  Copyright © 2020 Dawn Wang. All rights reserved.
//

#import "SKeyboardManager.h"
#import "NSObject+TopVC.h"
#import "UIView+FirstResponder.h"



/*全局键盘调用的整体思路
1:获取当前VC:topVC
2:获取当前弹出键盘的视图firstResponderView
3:获取需要做滚动的视图moveView
3.1:找到firstResponderView的supperView是否有具有滚动属性的view
3.2:如果有scrollEbleMoveView则直接返回,即moveView = scrollEbleMoveView,如果没有则返回topVC.view,即moveView = topVC.view
3.3:监听scrollEbleMoveView的contentOffset属性,当属性变化的时候,调用[firstResponderView resignFirstResponder](待定,因为resignFirstResponder也可以在外面使用)
4:将firstResponderView的rect映射到moveView上,映射后的结果是convertRect
5:判断convertRect和键盘frame的位置,并让moveView做相应的移动
*/
@interface SKeyboardManager()
<
UIGestureRecognizerDelegate
>

@property (nonatomic, weak) UIViewController *currentTopVC;
@property (nonatomic, strong) UITapGestureRecognizer *tapGesture;
@property (nonatomic, assign) CGFloat moveDistance;

@end
@implementation SKeyboardManager



- (void)moveViewsForKeyboardInfo:(NSNotification *)noti {
    NSDictionary *userInfo = noti.userInfo;
    //1:获取当前VC:topVC
    UIViewController *topVC = [self s_topViewController];
    self.currentTopVC = topVC;
    //2:获取当前弹出键盘的视图firstResponderView
    UIView *firstResponderView = [topVC.view s_firstResponderView];
    //3:获取需要做滚动的视图moveView
    UIView *moveView = [self supperNeedMoveViewFrom:firstResponderView];
    //如果是可滚动视图,给个监听,获取键盘弹出时最后的偏移量,方便键盘消失时恢复原来的样子
    WeakSelf
    if ([moveView isKindOfClass:[UIScrollView class]]) {
        [[moveView rac_valuesForKeyPath:@"contentOffset" observer:nil] subscribeNext:^(id  _Nullable x) {
            if (x) {
                StrongSelf;
                NSValue *value = (NSValue *)x;
                CGPoint point = [value CGPointValue];
                self.moveDistance = point.y;
            }
        }];
    }
    //4:将firstResponderView的rect映射到moveView上,映射后的结果是convertRect
    //此处不映射到moveView上是因为键盘打高度是window坐标,而且当前页面没有隐藏导航,或者scrollview位置偏下会造成计算错误
    CGRect convertRect = [firstResponderView convertRect:firstResponderView.bounds toView:[UIApplication sharedApplication].keyWindow];
  
    //5:判断convertRect和键盘frame的位置,并让moveView做相应的移动
    CGRect originalFrame = moveView.frame;
    if ([noti.name isEqualToString:UIKeyboardWillChangeFrameNotification]) {
        CGRect keyboardFameEnd = [userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
        CGFloat movedDistance = convertRect.origin.y + convertRect.size.height - keyboardFameEnd.origin.y;
        //键盘frame有变化且需要移动
        if (movedDistance > 0) {
            if ([moveView isMemberOfClass:[UIScrollView class]]) {
                //如果是可移动视图,直接移动
                UIScrollView *scrollView = (UIScrollView *)moveView;
                [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y + movedDistance) animated:YES];
             }else{
                 //如果是vc.view则重新设置origin.y
                 [UIView animateWithDuration:[userInfo[UIKeyboardAnimationDurationUserInfoKey] integerValue] animations:^{
                     moveView.frame = CGRectMake(CGRectGetMinX(originalFrame), originalFrame.origin.y - movedDistance, CGRectGetWidth(originalFrame), CGRectGetHeight(originalFrame));
                 } completion:^(BOOL finished) {
                 }];
             }
        }
    }

    if ([noti.name isEqualToString:UIKeyboardWillHideNotification]) {
             
        if ([moveView isMemberOfClass:[UIScrollView class]]) {
            UIScrollView *scrollView = (UIScrollView *)moveView;
            [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, scrollView.contentOffset.y - self.moveDistance) animated:YES];
        }else{
            
            [UIView animateWithDuration:[userInfo[UIKeyboardAnimationDurationUserInfoKey] integerValue] animations:^{
                moveView.frame = CGRectMake(CGRectGetMinX(originalFrame), 0, CGRectGetWidth(moveView.frame), CGRectGetHeight(moveView.frame));
            } completion:^(BOOL finished) {
            }];
            
        }

    }
 
    
    //topvc tap 收起键盘
    if (![topVC.view.gestureRecognizers containsObject:self.tapGesture]) {
        UITapGestureRecognizer *tapGes = [[UITapGestureRecognizer alloc] init];
        [topVC.view addGestureRecognizer:tapGes];
        tapGes.delegate = self;
        self.tapGesture = tapGes;
    }
    __weak typeof(firstResponderView) weakFirstView = firstResponderView;
    [[self.tapGesture rac_gestureSignal] subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
        __strong typeof(weakFirstView) strongFirstView = weakFirstView;
        [strongFirstView resignFirstResponder];
    }];
}

//3.1:找到firstResponderView的supperView是否有具有滚动属性的view
//3.2:如果有scrollEbleMoveView则直接返回,即moveView = scrollEbleMoveView,如果没有则返回topVC.view,即moveView = topVC.view
- (UIView *)supperNeedMoveViewFrom:(UIView *)view {
    //TODO::辨别可滚动视图
    UIView *tempSupperView = view;
    while (![NSStringFromClass(tempSupperView.classForCoder) isEqualToString:@"UIViewControllerWrapperView"]) {
        if ([tempSupperView isMemberOfClass:[UIScrollView class]]) {
            return tempSupperView;
        }else{
            tempSupperView = tempSupperView.superview;
        }
    }
        
    return self.currentTopVC.view;
}
//此处修复了一个bug是,UICollectionViewCell的点击操作跟我的键盘收回的手势冲突了
#pragma mark - UIGestureRecognizerDelegate
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
    UIView *touchView = touch.view;
    BOOL findCell = YES;
    while (![touchView.nextResponder isKindOfClass:[UICollectionViewCell class]]) {
        if ([NSStringFromClass(touchView.classForCoder) isEqualToString:@"UIViewControllerWrapperView"]) {
            findCell = NO;
            break;
        }
        touchView = (UIView *)touchView.nextResponder;
    }
    return !findCell;
}
@end

//
//  NSObject+TopVC.m
//  Snatch
//
//  Created by DawnWang on 2020/2/9.
//  Copyright © 2020 Dawn Wang. All rights reserved.
//

#import "NSObject+TopVC.h"


@implementation NSObject (TopVC)
- (UIViewController *)s_topViewController {
    UIViewController *currentVC = nil;
    UIWindow *window = [UIApplication sharedApplication].keyWindow;
    if (window) {
        currentVC = window.rootViewController;
    }
    return [self scanController:currentVC];
}

- (UIViewController *)scanController:(UIViewController *)viewController {
    if (!viewController) {
        return nil;
    }
    UIViewController *currentVC = nil;
    if ([viewController isKindOfClass:[UINavigationController class]] && ([((UINavigationController *)viewController) topViewController] != nil)) {
        //当前根试图是navigationVCm,且topVC不为空
        currentVC = [self scanController:[(UINavigationController *)viewController topViewController]];
    }else if ([viewController isKindOfClass:[UITabBarController class]] && ([((UITabBarController *)viewController)  selectedViewController] != nil)){
        //当前根试图是tabbar vc,且selectedVC不为空
        currentVC = [self scanController:[(UITabBarController *)viewController selectedViewController]];
    }else{
        //当前试图是一个标准UIViewController
        currentVC = viewController;
        //判断是否是present出来的试图
        BOOL isPresentController = NO;
        UIViewController *presentVC = currentVC.presentedViewController;
        while (presentVC) {
            currentVC = presentVC;
            isPresentController = YES;
            presentVC = currentVC.presentedViewController;
        }
        if (isPresentController) {
            currentVC = [self scanController:currentVC];
        }
    }
    return currentVC;
}

@end

//
//  UIView+FirstResponder.m
//  Snatch
//
//  Created by DawnWang on 2020/2/9.
//  Copyright © 2020 Dawn Wang. All rights reserved.
//

#import "UIView+FirstResponder.h"

@implementation UIView (FirstResponder)
- (UIView * __nullable)s_firstResponderView {
    if ([self isFirstResponder]) {
        return self;
    }
    UIView *firstView = nil;
    for (UIView *subView in self.subviews) {
        firstView = [subView s_firstResponderView];
        if ([firstView isFirstResponder]) {
            break;
        }
    }
    return firstView;
}

@end

好了,代码罗列完毕。

- (UIViewController *)popViewControllerAnimated:(BOOL)animated {
    //fix bug : 当前VC中尚有textfield调起键盘时后返回,导致的:XPC connection interrupted错误
    UIViewController * topVC = [self topViewController];
    UIView *firstResponse = [topVC.view s_firstResponderView];
    [firstResponse resignFirstResponder];
    return [super popViewControllerAnimated:animated];
}

最后附上一个小知识点:键盘通知顺序

上一篇 下一篇

猜你喜欢

热点阅读