Skip to content

Commit

Permalink
1、整理 QMUIKeyboardManager 的注释和接口;2、优化自定义 navigationBar 的代码里有些 if 判断写法
Browse files Browse the repository at this point in the history
  • Loading branch information
MoLice committed Apr 12, 2017
1 parent a7cb123 commit c510c6d
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 124 deletions.
152 changes: 79 additions & 73 deletions QMUIKit/UIComponents/QMUIKeyboardManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,97 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@protocol QMUIKeyboardManagerDelegate;
@class QMUIKeyboardUserInfo;
@class QMUIKeyboardManager;


/**
* `QMUIKeyboardManagerDelegate`里面的方法是对应系统键盘通知的回调方法,具体请看delegate名字,`QMUIKeyboardUserInfo`是对系统的userInfo做了一个封装,可以方便的获取userInfo的属性值。
* `QMUIKeyboardManager` 提供了方便管理键盘事件的方案,使用的场景是需要跟随键盘的显示或者隐藏来更改界面的 UI,例如输入框跟随在键盘的顶部。
* 由于键盘通知是整个 App 全局的,所以经常会遇到 A 的键盘监听回调里接收到 B 的键盘事件,这样的情况往往不是我们想要的,即使可以通过判断当前的 firstResponder 来区分,但还是不能完美的解决问题或者有时候解决起来非常麻烦。`QMUIKeyboardManager` 通过 `delegateEnabled` 和 `targetResponder` 等属性来方便地控制 firstResponder,从而可以实现某个键盘监听回调方法只响应某个 UIResponder 或者某几个 UIResponder 触发的键盘通知。
*/
@protocol QMUIKeyboardManagerDelegate <NSObject>
@interface QMUIKeyboardManager : NSObject

@optional
/**
* 指定初始化方法,以 delegate 的方式将键盘事件传递给监听者
*/
- (instancetype)initWithDelegate:(id<QMUIKeyboardManagerDelegate>)delegate NS_DESIGNATED_INITIALIZER;

/**
* 键盘即将显示
* 获取当前的 delegate
*/
- (void)keyboardWillShowWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;
@property(nonatomic, weak, readonly) id<QMUIKeyboardManagerDelegate> delegate;

/**
* 键盘即将隐藏
* 是否允许触发delegate的回调,某些场景可能要主动停止对键盘事件的响应。
* 默认为 YES。
*/
- (void)keyboardWillHideWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;
@property(nonatomic, assign) BOOL delegateEnabled;

/**
* 键盘frame即将发生变化
* 这个delegate除了对应系统的willChangeFrame通知外,在iPad下还增加了监听键盘frame变化的KVO来处理浮动键盘,所以调用次数会比系统默认多。需要让界面或者某个view跟随键盘运动,建议在这个通知delegate里面实现,因为willShow和willHide在手机上是准确的,但是在iPad的浮动键盘下是不准确的。另外,如果不需要跟随浮动键盘运动,那么在逻辑代码里面可以通过判断键盘的位置来过滤这种浮动的情况
* 添加触发键盘事件的 UIResponder,一般是 UITextView 或者 UITextField ,不添加 targetResponder 的话,则默认接受任何 UIResponder 产生的键盘通知
* 添加成功将会返回YES,否则返回NO
*/
- (void)keyboardWillChangeFrameWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;
- (BOOL)addTargetResponder:(UIResponder *)targetResponder;

/**
* 键盘已经显示
* 获取当前所有的 target UIResponder,若不存在则返回 nil
*/
- (void)keyboardDidShowWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;
- (NSArray<UIResponder *> *)allTargetResponders;

/**
* 键盘已经隐藏
* 把键盘的rect转为相对于view的rect。一般用来把键盘的rect转化为相对于当前 self.view 的 rect,然后获取 y 值来布局对应的 view(这里一般不要获取键盘的高度,因为对于iPad的键盘,浮动状态下键盘的高度往往不是我们想要的)。
* @param rect 键盘的rect,一般拿 keyboardUserInfo.endFrame
* @param view 一个特定的view或者window,如果传入nil则相对有当前的 mainWindow
*/
- (void)keyboardDidHideWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;
+ (CGRect)convertKeyboardRect:(CGRect)rect toView:(UIView *)view;

/**
* 键盘frame已经发生变化
* 获取键盘到顶部到相对于view底部的距离,这个值在某些情况下会等于endFrame.size.height或者visiableKeyboardHeight,不过在iPad浮动键盘的时候就包括了底部的空隙。所以建议使用这个方法
*/
- (void)keyboardDidChangeFrameWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;
+ (CGFloat)distanceFromMinYToBottomInView:(UIView *)view keyboardRect:(CGRect)rect;

/**
* 根据键盘的动画参数自己构建一个动画,调用者只需要设置view的位置即可
*/
+ (void)animateWithAnimated:(BOOL)animated keyboardUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;

/**
* 这个方法特殊处理 iPad Pro 外接键盘的情况。使用外接键盘在完全不显示键盘的时候,不会调用willShow的通知,所以导致一些通过willShow回调来显示targetResponder的场景(例如微信朋友圈的评论输入框)无法把targetResponder正常的显示出来。通过这个方法,你只需要关心你的show和hide的状态就好了,不需要关心是否 iPad Pro 的情况。
* @param showBlock 键盘显示回调的block,不能把showBlock理解为系统的show通知,而是你有输入框聚焦了并且期望键盘显示出来。
* @param hideBlock 键盘隐藏回调的block,不能把hideBlock理解为系统的hide通知,而是键盘即将消失在界面上并且你期望跟随键盘变化的UI回到默认状态。
*/
+ (void)handleKeyboardNotificationWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo showBlock:(void (^)(QMUIKeyboardUserInfo *keyboardUserInfo))showBlock hideBlock:(void (^)(QMUIKeyboardUserInfo *keyboardUserInfo))hideBlock;

/**
* 键盘面板的私有view,可能为nil
*/
+ (UIView *)keyboardView;

/**
* 键盘面板所在的私有window,可能为nil
*/
+ (UIWindow *)keyboardWindow;

/**
* 是否有键盘在显示
*/
+ (BOOL)isKeyboardVisible;

/**
* 当期那键盘相对于屏幕的frame
*/
+ (CGRect)currentKeyboardFrame;

/**
* 当前键盘高度键盘的可见高度
*/
+ (CGFloat)visiableKeyboardHeight;

@end




@interface QMUIKeyboardUserInfo : NSObject

/**
Expand Down Expand Up @@ -119,84 +165,44 @@
@end


/**
* `QMUIKeyboardManager`提供了方便管理键盘事件的方案,使用的场景是需要跟随键盘的显示或者隐藏来更改界面的UI原生,例如键盘顶部跟随一个输入框。
* 由于键盘通知的全局性,经常会遇到一个地方的键盘监听回调接响应了其他界面或者控件触发的键盘通知,这样的情况往往不是我们想要的,即使可以通过一些其他的方法来避免,但还是不能完美的解决问题或者有时候解决起来非常麻烦。`QMUIKeyboardManager`通过`delegateEnabled`和`targetResponder`等增强功能属性来方便的控制响应的对象,从而可以实现某个键盘监听回调方法只响应某个UIResponder或者某几个UIResponder触发的键盘通知。
*/
@interface QMUIKeyboardManager : NSObject

/**
* 获取delegate
*/
@property(nonatomic, weak, readonly) id <QMUIKeyboardManagerDelegate> delegate;

/**
* 是否允许触发delegate的回调,用来某些场景直接关闭某些界面或者控件里面的键盘回调来临时禁止接受键盘通知事件。默认YES
*/
@property(nonatomic, assign) BOOL delegateEnabled;

/**
* 添加触发键盘事件的UIResponder,一般是UITextView或者UITextField,没有targetResponder则默认接受任何UIResponder产生的键盘通知。添加成功将会返回YES,否则返回NO。
*/
- (BOOL)addTargetResponder:(UIResponder *)targetResponder;

/**
* 获取当前所有的 target UIResponder
*/
- (NSMutableArray <UIResponder *> *)targetResponders;

/**
* 唯一初始化方法
*/
- (instancetype)initWithDelegate:(id <QMUIKeyboardManagerDelegate>)delegate NS_DESIGNATED_INITIALIZER;

/**
* 把键盘的rect转为相对于view的rect。一般用来把键盘的rect转化为相对于当前self.view的rect,然后获取y值来布局跟随在键盘上的输入框等等(这里一般不要获取键盘的高度,因为对于iPad的键盘,浮动状态下键盘的高度往往不是我们想要的)。
* @param rect 键盘的rect,一般拿keyboardUserInfo.endFrame
* @param view 一个特定的view或者window,如果传入nil则相对有当前的 mainWindow
*/
+ (CGRect)convertKeyboardRect:(CGRect)rect toView:(UIView *)view;

/**
* 获取键盘到顶部到相对于view底部的距离,这个值在某些情况下会等于endFrame.size.height或者visiableKeyboardHeight,不过在iPad浮动键盘的时候就包括了底部的空隙。所以建议使用这个方法。
* `QMUIKeyboardManagerDelegate`里面的方法是对应系统键盘通知的回调方法,具体请看delegate名字,`QMUIKeyboardUserInfo`是对系统的userInfo做了一个封装,可以方便的获取userInfo的属性值。
*/
+ (CGFloat)distanceFromMinYToBottomInView:(UIView *)view keyboardRect:(CGRect)rect;
@protocol QMUIKeyboardManagerDelegate <NSObject>

/**
* 根据键盘的动画参数自己构建一个动画,调用者只需要设置view的位置即可
*/
+ (void)animateWithAnimated:(BOOL)animated keyboardUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion;
@optional

/**
* 这个方法特殊处理 iPad Pro 外接键盘的情况。使用外接键盘在完全不显示键盘的时候,不会调用willShow的通知,所以导致一些通过willShow回调来显示targetResponder的场景(例如微信朋友圈的评论输入框)无法把targetResponder正常的显示出来。通过这个方法,你只需要关心你的show和hide的状态就好了,不需要关心是否 iPad Pro 的情况。
* @param showBlock 键盘显示回调的block,不能把showBlock理解为系统的show通知,而是你有输入框聚焦了并且期望键盘显示出来。
* @param hideBlock 键盘隐藏回调的block,不能把hideBlock理解为系统的hide通知,而是键盘即将消失在界面上并且你期望跟随键盘变化的UI回到默认状态。
* 键盘即将显示
*/
+ (void)handleKeyboardNotificationWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo showBlock:(void (^)(QMUIKeyboardUserInfo *keyboardUserInfo))showBlock hideBlock:(void (^)(QMUIKeyboardUserInfo *keyboardUserInfo))hideBlock;
- (void)keyboardWillShowWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;

/**
* 键盘面板的私有view,可能为nil
* 键盘即将隐藏
*/
+ (UIView *)keyboardView;
- (void)keyboardWillHideWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;

/**
* 键盘面板所在的私有window,可能为nil
* 键盘frame即将发生变化。
* 这个delegate除了对应系统的willChangeFrame通知外,在iPad下还增加了监听键盘frame变化的KVO来处理浮动键盘,所以调用次数会比系统默认多。需要让界面或者某个view跟随键盘运动,建议在这个通知delegate里面实现,因为willShow和willHide在手机上是准确的,但是在iPad的浮动键盘下是不准确的。另外,如果不需要跟随浮动键盘运动,那么在逻辑代码里面可以通过判断键盘的位置来过滤这种浮动的情况。
*/
+ (UIWindow *)keyboardWindow;
- (void)keyboardWillChangeFrameWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;

/**
* 是否有键盘在显示
* 键盘已经显示
*/
+ (BOOL)isKeyboardVisible;
- (void)keyboardDidShowWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;

/**
* 当期那键盘相对于屏幕的frame
* 键盘已经隐藏
*/
+ (CGRect)currentKeyboardFrame;
- (void)keyboardDidHideWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;

/**
* 当前键盘高度键盘的可见高度
* 键盘frame已经发生变化。
*/
+ (CGFloat)visiableKeyboardHeight;
- (void)keyboardDidChangeFrameWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo;

@end
65 changes: 22 additions & 43 deletions QMUIKit/UIComponents/QMUIKeyboardManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -299,15 +299,18 @@ - (BOOL)addTargetResponder:(UIResponder *)targetResponder {
return YES;
}

- (NSMutableArray <UIResponder *> *)targetResponders {
NSMutableArray *targetResponders = [[NSMutableArray alloc] init];
- (NSArray<UIResponder *> *)allTargetResponders {
NSMutableArray *targetResponders = nil;
for (int i = 0; i < self.targetResponderValues.count; i++) {
if (!targetResponders) {
targetResponders = [[NSMutableArray alloc] init];
}
id unPackageValue = [self unPackageTargetResponder:self.targetResponderValues[i]];
if (unPackageValue && [unPackageValue isKindOfClass:[UIResponder class]]) {
[targetResponders addObject:(UIResponder *)unPackageValue];
}
}
return targetResponders;
return [targetResponders copy];
}

- (NSValue *)packageTargetResponder:(UIResponder *)targetResponder {
Expand Down Expand Up @@ -648,8 +651,6 @@ + (void)handleKeyboardNotificationWithUserInfo:(QMUIKeyboardUserInfo *)keyboardU

+ (UIWindow *)keyboardWindow {

// 这个方法参考YYKyeboardManager:https://github.com/ibireme/YYKeyboardManager/blob/master/YYKeyboardManager/YYKeyboardManager.m

for (UIWindow *window in [UIApplication sharedApplication].windows) {
if ([self getKeyboardViewFromWindow:window]) {
return window;
Expand Down Expand Up @@ -688,8 +689,6 @@ + (UIWindow *)keyboardWindow {

+ (CGRect)convertKeyboardRect:(CGRect)rect toView:(UIView *)view {

// 这个方法参考YYKyeboardManager:https://github.com/ibireme/YYKeyboardManager/blob/master/YYKeyboardManager/YYKeyboardManager.m

if (CGRectIsNull(rect) || CGRectIsInfinite(rect)) {
return rect;
}
Expand Down Expand Up @@ -744,59 +743,39 @@ + (UIView *)keyboardView {

+ (UIView *)getKeyboardViewFromWindow:(UIWindow *)window {

// 这个方法参考YYKyeboardManager:https://github.com/ibireme/YYKeyboardManager/blob/master/YYKeyboardManager/YYKeyboardManager.m

/*
iOS 6/7:
UITextEffectsWindow
UIPeripheralHostView << keyboard
iOS 8:
UITextEffectsWindow
UIInputSetContainerView
UIInputSetHostView << keyboard
iOS 9:
UIRemoteKeyboardWindow
UIInputSetContainerView
UIInputSetHostView << keyboard
*/

if (!window) return nil;

NSString *windowName = NSStringFromClass(window.class);
if (IOS_VERSION < 9) {
// UITextEffectsWindow
if (windowName.length != 19) return nil;
if (![windowName hasPrefix:@"UI"]) return nil;
if (![windowName hasSuffix:[NSString stringWithFormat:@"%@%@", @"TextEffects", @"Window"]]) return nil;
if (![windowName isEqualToString:@"UITextEffectsWindow"]) {
return nil;
}
} else {
// UIRemoteKeyboardWindow
if (windowName.length != 22) return nil;
if (![windowName hasPrefix:@"UI"]) return nil;
if (![windowName hasSuffix:[NSString stringWithFormat:@"%@%@", @"RemoteKeyboard", @"Window"]]) return nil;
if (![windowName isEqualToString:@"UIRemoteKeyboardWindow"]) {
return nil;
}
}

if (IOS_VERSION < 8) {
// UIPeripheralHostView
for (UIView *view in window.subviews) {
NSString *viewName = NSStringFromClass(view.class);
if (viewName.length != 20) continue;
if (![viewName hasPrefix:@"UI"]) continue;
if (![viewName hasSuffix:[NSString stringWithFormat:@"%@%@", @"Peripheral", @"HostView"]]) continue;
if (![viewName isEqualToString:@"UIPeripheralHostView"]) {
continue;
}
return view;
}
} else {
// UIInputSetContainerView
for (UIView *view in window.subviews) {
NSString *viewName = NSStringFromClass(view.class);
if (viewName.length != 23) continue;
if (![viewName hasPrefix:@"UI"]) continue;
if (![viewName hasSuffix:[NSString stringWithFormat:@"%@%@", @"InputSet", @"ContainerView"]]) continue;
// UIInputSetHostView
if (![viewName isEqualToString:@"UIInputSetContainerView"]) {
continue;
}

for (UIView *subView in view.subviews) {
NSString *subViewName = NSStringFromClass(subView.class);
if (subViewName.length != 18) continue;
if (![subViewName hasPrefix:@"UI"]) continue;
if (![subViewName hasSuffix:[NSString stringWithFormat:@"%@%@", @"InputSet", @"HostView"]]) continue;
if (![subViewName isEqualToString:@"UIInputSetHostView"]) {
continue;
}
return subView;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,23 +476,23 @@ - (UIViewController *)NavigationBarTransition_popViewControllerAnimated:(BOOL)an
- (NSArray<UIViewController *> *)NavigationBarTransition_popToViewController:(UIViewController *)viewController animated:(BOOL)animated {
UIViewController *disappearingViewController = self.viewControllers.lastObject;
UIViewController *appearingViewController = viewController;
if (!disappearingViewController) {
[self NavigationBarTransition_popToViewController:viewController animated:animated];
NSArray<UIViewController *> *poppedViewControllers = [self NavigationBarTransition_popToViewController:viewController animated:animated];
if (poppedViewControllers) {
[self handlePopViewControllerNavigationBarTransitionWithDisappearViewController:disappearingViewController appearViewController:appearingViewController];
}
[self handlePopViewControllerNavigationBarTransitionWithDisappearViewController:disappearingViewController appearViewController:appearingViewController];
return [self NavigationBarTransition_popToViewController:viewController animated:animated];
return poppedViewControllers;
}

- (NSArray<UIViewController *> *)NavigationBarTransition_popToRootViewControllerAnimated:(BOOL)animated {
NSArray<UIViewController *> *poppedViewControllers = [self NavigationBarTransition_popToRootViewControllerAnimated:animated];
if (self.viewControllers.count > 1) {
UIViewController *disappearingViewController = self.viewControllers.lastObject;
UIViewController *appearingViewController = self.viewControllers.firstObject;
if (!disappearingViewController) {
[self NavigationBarTransition_popToRootViewControllerAnimated:animated];
if (poppedViewControllers) {
[self handlePopViewControllerNavigationBarTransitionWithDisappearViewController:disappearingViewController appearViewController:appearingViewController];
}
[self handlePopViewControllerNavigationBarTransitionWithDisappearViewController:disappearingViewController appearViewController:appearingViewController];
}
return [self NavigationBarTransition_popToRootViewControllerAnimated:animated];
return poppedViewControllers;
}

- (void)handlePopViewControllerNavigationBarTransitionWithDisappearViewController:(UIViewController *)disappearViewController appearViewController:(UIViewController *)appearViewController {
Expand Down

0 comments on commit c510c6d

Please sign in to comment.