1818#import " QMUILog.h"
1919#import " QMUIAppearance.h"
2020#import " QMUIMultipleDelegates.h"
21+ #import " NSArray+QMUI.h"
2122
2223@class QMUIKeyboardViewFrameObserver;
2324@protocol QMUIKeyboardViewFrameObserverDelegate <NSObject >
@@ -630,14 +631,10 @@ - (QMUIKeyboardUserInfo *)newUserInfoWithNotification:(NSNotification *)notifica
630631}
631632
632633- (BOOL )shouldReceiveShowNotification {
633-
634634 UIResponder *firstResponder = [self firstResponderInWindows ];
635- if (self.currentResponder ) {
636- // 这里有 BUG,如果点击了 webview 导致键盘下降,这个时候运行 shouldReceiveHideNotification 就会判断错误,所以如果发现是 nil 或是 WKContentView 则值不变
637- if (firstResponder && ![firstResponder isKindOfClass: NSClassFromString ([NSString stringWithFormat: @" %@%@ " , @" WK" , @" ContentView" ])]) {
638- self.currentResponder = firstResponder;
639- }
640- } else {
635+ // 如果点击了 webview 导致键盘下降,这个时候运行 shouldReceiveHideNotification 就会判断错误,所以如果发现是 nil 或是 WKContentView 则值不变
636+ // WKContentView
637+ if (!self.currentResponder || (firstResponder && ![firstResponder isKindOfClass: NSClassFromString ([NSString stringWithFormat: @" %@%@ " , @" WK" , @" ContentView" ])])) {
641638 self.currentResponder = firstResponder;
642639 }
643640
@@ -713,7 +710,7 @@ - (void)keyboardDidChangedFrame:(UIView *)keyboardView {
713710 keyboardMoveUserInfo.animationOptions = self.lastUserInfo ? self.lastUserInfo .animationOptions : keyboardMoveUserInfo.animationCurve <<16 ;
714711 keyboardMoveUserInfo.beginFrame = self.keyboardMoveBeginRect ;
715712 keyboardMoveUserInfo.endFrame = endFrame;
716- keyboardMoveUserInfo.isFloatingKeyboard = CGRectGetWidth ([ self .class keyboardView ] .bounds ) < CGRectGetWidth ([UIScreen mainScreen ]. bounds );
713+ keyboardMoveUserInfo.isFloatingKeyboard = keyboardView ? CGRectGetWidth (keyboardView.bounds ) < CGRectGetWidth (UIApplication. sharedApplication . delegate . window . bounds ) : NO ;
717714
718715 if (self.debug ) {
719716 NSLog (@" keyboardDidMoveNotification - %@ \n " , self);
@@ -724,7 +721,7 @@ - (void)keyboardDidChangedFrame:(UIView *)keyboardView {
724721 self.keyboardMoveBeginRect = endFrame;
725722
726723 if (self.currentResponder ) {
727- UIWindow *mainWindow = UIApplication.sharedApplication .keyWindow ?: UIApplication.sharedApplication .windows . firstObject ;
724+ UIWindow *mainWindow = UIApplication.sharedApplication .keyWindow ?: UIApplication.sharedApplication .delegate . window ;
728725 if (mainWindow) {
729726 CGRect keyboardRect = keyboardMoveUserInfo.endFrame ;
730727 CGFloat distanceFromBottom = [QMUIKeyboardManager distanceFromMinYToBottomInView: mainWindow keyboardRect: keyboardRect];
@@ -791,39 +788,13 @@ + (void)handleKeyboardNotificationWithUserInfo:(QMUIKeyboardUserInfo *)keyboardU
791788 }
792789}
793790
794- + (UIWindow *)keyboardWindow {
795-
796- for (UIWindow *window in UIApplication.sharedApplication .windows ) {
797- if ([self getKeyboardViewFromWindow: window]) {
798- return window;
799- }
800- }
801-
802- NSMutableArray *kbWindows = nil ;
803-
804- for (UIWindow *window in UIApplication.sharedApplication .windows ) {
805- NSString *windowName = NSStringFromClass (window.class );
806- if ([windowName isEqualToString: [NSString stringWithFormat: @" UI%@%@ " , @" Remote" , @" KeyboardWindow" ]]) {
807- // UIRemoteKeyboardWindow(iOS9 以下 UITextEffectsWindow)
808- if (!kbWindows) kbWindows = [NSMutableArray new ];
809- [kbWindows addObject: window];
810- }
811- }
812-
813- if (kbWindows.count == 1 ) {
814- return kbWindows.firstObject ;
815- }
816-
817- return nil ;
818- }
819-
820791+ (CGRect)convertKeyboardRect : (CGRect)rect toView : (UIView *)view {
821792
822793 if (CGRectIsNull (rect) || CGRectIsInfinite (rect)) {
823794 return rect;
824795 }
825796
826- UIWindow *mainWindow = UIApplication.sharedApplication .keyWindow ?: UIApplication.sharedApplication .windows . firstObject ;
797+ UIWindow *mainWindow = UIApplication.sharedApplication .keyWindow ?: UIApplication.sharedApplication .delegate . window ;
827798 if (!mainWindow) {
828799 if (view) {
829800 [view convertRect: rect fromView: nil ];
@@ -861,39 +832,80 @@ + (CGFloat)distanceFromMinYToBottomInView:(UIView *)view keyboardRect:(CGRect)re
861832 return distance;
862833}
863834
835+ + (UIWindow *)keyboardWindow {
836+ for (UIWindow *window in UIApplication.sharedApplication .windows ) {
837+ if ([self positionedKeyboardViewInWindow: window]) {
838+ return window;
839+ }
840+ }
841+
842+ UIWindow *window = [UIApplication.sharedApplication.windows qmui_firstMatchWithBlock: ^BOOL (__kindof UIWindow * _Nonnull item) {
843+ return [NSStringFromClass (item.class) isEqualToString: @" UIRemoteKeyboardWindow" ];
844+ }];
845+ if (window) {
846+ return window;
847+ }
848+
849+ window = [UIApplication.sharedApplication.windows qmui_firstMatchWithBlock: ^BOOL (__kindof UIWindow * _Nonnull item) {
850+ return [NSStringFromClass (item.class) isEqualToString: @" UITextEffectsWindow" ];
851+ }];
852+ return window;
853+ }
854+
864855+ (UIView *)keyboardView {
865856 for (UIWindow *window in UIApplication.sharedApplication .windows ) {
866- UIView *view = [self getKeyboardViewFromWindow : window];
857+ UIView *view = [self positionedKeyboardViewInWindow : window];
867858 if (view) {
868859 return view;
869860 }
870861 }
871862 return nil ;
872863}
873864
874- + (UIView *)getKeyboardViewFromWindow : (UIWindow *)window {
865+ /* *
866+ 从给定的 window 里寻找代表键盘当前布局位置的 view。
867+ iOS 15 及以前(包括用 Xcode 13 编译的 App 运行在 iOS 16 上的场景),键盘的 UI 层级是:
868+ |- UIApplication.windows
869+ |- UIRemoteKeyboardWindow
870+ |- UIInputSetContainerView
871+ |- UIInputSetHostView - 键盘及 webView 里的输入工具栏(上下键、Done键)
872+ |- _UIKBCompatInputView - 键盘主体按键
873+ |- TUISystemInputAssistantView - 键盘顶部的候选词栏、emoji 键盘顶部的搜索框
874+ |- _UIRemoteKeyboardPlaceholderView - webView 里的输入工具栏的占位(实际的 view 在 UITextEffectsWindow 里)
875+
876+ iOS 16 及以后(仅限用 Xcode 14 及以上版本编译的 App),UIApplication.windows 里已经不存在 UIRemoteKeyboardWindow 了,所以退而求其次,我们通过 UITextEffectsWindow 里的 UIInputSetHostView 来获取键盘的位置——这两个 window 在布局层面可以理解为镜像关系。
877+ |- UIApplication.windows
878+ |- UITextEffectsWindow
879+ |- UIInputSetContainerView
880+ |- UIInputSetHostView - 键盘及 webView 里的输入工具栏(上下键、Done键)
881+ |- _UIRemoteKeyboardPlaceholderView - 整个键盘区域,包含顶部候选词栏、emoji 键盘顶部搜索栏(有时候不一定存在)
882+ |- UIWebFormAccessory - webView 里的输入工具栏的占位
883+ |- TUIInputAssistantHostView - 外接键盘时可能存在,此时不一定有 placeholder
884+ |- UIInputSetHostView - 可能存在多个,但只有一个里面有 _UIRemoteKeyboardPlaceholderView
885+
886+ 所以只要找到 UIInputSetHostView 即可,优先从 UIRemoteKeyboardWindow 找,不存在的话则从 UITextEffectsWindow 找。
887+ */
888+ + (UIView *)positionedKeyboardViewInWindow : (UIWindow *)window {
875889
876890 if (!window) return nil ;
877891
878892 NSString *windowName = NSStringFromClass (window.class );
879- if (![windowName isEqualToString: @" UIRemoteKeyboardWindow" ]) {
880- return nil ;
893+ if ([windowName isEqualToString: @" UIRemoteKeyboardWindow" ]) {
894+ UIView *result = [[window.subviews qmui_firstMatchWithBlock: ^BOOL (__kindof UIView * _Nonnull subview) {
895+ return [NSStringFromClass (subview.class) isEqualToString: @" UIInputSetContainerView" ];
896+ }].subviews qmui_firstMatchWithBlock: ^BOOL (__kindof UIView * _Nonnull subview) {
897+ return [NSStringFromClass (subview.class) isEqualToString: @" UIInputSetHostView" ];
898+ }];
899+ return result;
881900 }
882-
883- for (UIView *view in window.subviews ) {
884- NSString *viewName = NSStringFromClass (view.class );
885- if (![viewName isEqualToString: @" UIInputSetContainerView" ]) {
886- continue ;
887- }
888- for (UIView *subView in view.subviews ) {
889- NSString *subViewName = NSStringFromClass (subView.class );
890- if (![subViewName isEqualToString: @" UIInputSetHostView" ]) {
891- continue ;
892- }
893- return subView;
894- }
901+ if ([windowName isEqualToString: @" UITextEffectsWindow" ]) {
902+ UIView *result = [[window.subviews qmui_firstMatchWithBlock: ^BOOL (__kindof UIView * _Nonnull subview) {
903+ return [NSStringFromClass (subview.class) isEqualToString: @" UIInputSetContainerView" ];
904+ }].subviews qmui_firstMatchWithBlock: ^BOOL (__kindof UIView * _Nonnull subview) {
905+ return [NSStringFromClass (subview.class) isEqualToString: @" UIInputSetHostView" ] && subview.subviews .count ;
906+ }];
907+ return result;
895908 }
896-
897909 return nil ;
898910}
899911
0 commit comments