123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- //
- // YYTextKeyboardManager.m
- // YYKit <https://github.com/ibireme/YYKit>
- //
- // Created by ibireme on 15/6/3.
- // Copyright (c) 2015 ibireme.
- //
- // This source code is licensed under the MIT-style license found in the
- // LICENSE file in the root directory of this source tree.
- //
- #import "YYTextKeyboardManager.h"
- #import "UIApplication+YYAdd.h"
- #import <objc/runtime.h>
- static int _YYTextKeyboardViewFrameObserverKey;
- /// Observer for view's frame/bounds/center/transform
- @interface _YYTextKeyboardViewFrameObserver : NSObject
- @property (nonatomic, copy) void (^notifyBlock)(UIView *keyboard);
- - (void)addToKeyboardView:(UIView *)keyboardView;
- + (instancetype)observerForView:(UIView *)keyboardView;
- @end
- @implementation _YYTextKeyboardViewFrameObserver {
- __unsafe_unretained UIView *_keyboardView;
- }
- - (void)addToKeyboardView:(UIView *)keyboardView {
- if (_keyboardView == keyboardView) return;
- if (_keyboardView) {
- [self removeFrameObserver];
- objc_setAssociatedObject(_keyboardView, &_YYTextKeyboardViewFrameObserverKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- _keyboardView = keyboardView;
- if (keyboardView) {
- [self addFrameObserver];
- }
- objc_setAssociatedObject(keyboardView, &_YYTextKeyboardViewFrameObserverKey, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- - (void)removeFrameObserver {
- [_keyboardView removeObserver:self forKeyPath:@"frame"];
- [_keyboardView removeObserver:self forKeyPath:@"center"];
- [_keyboardView removeObserver:self forKeyPath:@"bounds"];
- [_keyboardView removeObserver:self forKeyPath:@"transform"];
- _keyboardView = nil;
- }
- - (void)addFrameObserver {
- if (!_keyboardView) return;
- [_keyboardView addObserver:self forKeyPath:@"frame" options:kNilOptions context:NULL];
- [_keyboardView addObserver:self forKeyPath:@"center" options:kNilOptions context:NULL];
- [_keyboardView addObserver:self forKeyPath:@"bounds" options:kNilOptions context:NULL];
- [_keyboardView addObserver:self forKeyPath:@"transform" options:kNilOptions context:NULL];
- }
- - (void)dealloc {
- [self removeFrameObserver];
- }
- + (instancetype)observerForView:(UIView *)keyboardView {
- if (!keyboardView) return nil;
- return objc_getAssociatedObject(keyboardView, &_YYTextKeyboardViewFrameObserverKey);
- }
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
-
- BOOL isPrior = [[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue];
- if (isPrior) return;
-
- NSKeyValueChange changeKind = [[change objectForKey:NSKeyValueChangeKindKey] integerValue];
- if (changeKind != NSKeyValueChangeSetting) return;
-
- id newVal = [change objectForKey:NSKeyValueChangeNewKey];
- if (newVal == [NSNull null]) newVal = nil;
-
- if (_notifyBlock) {
- _notifyBlock(_keyboardView);
- }
- }
- @end
- @implementation YYTextKeyboardManager {
- NSHashTable *_observers;
-
- CGRect _fromFrame;
- BOOL _fromVisible;
- UIInterfaceOrientation _fromOrientation;
-
- CGRect _notificationFromFrame;
- CGRect _notificationToFrame;
- NSTimeInterval _notificationDuration;
- UIViewAnimationCurve _notificationCurve;
- BOOL _hasNotification;
-
- CGRect _observedToFrame;
- BOOL _hasObservedChange;
-
- BOOL _lastIsNotification;
- }
- - (instancetype)init {
- @throw [NSException exceptionWithName:@"YYTextKeyboardManager init error" reason:@"Use 'defaultManager' to get instance." userInfo:nil];
- return [super init];
- }
- - (instancetype)_init {
- self = [super init];
- _observers = [[NSHashTable alloc] initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(_keyboardFrameWillChangeNotification:)
- name:UIKeyboardWillChangeFrameNotification
- object:nil];
- // for iPad (iOS 9)
- if ([UIDevice currentDevice].systemVersion.floatValue >= 9) {
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(_keyboardFrameDidChangeNotification:)
- name:UIKeyboardDidChangeFrameNotification
- object:nil];
- }
- return self;
- }
- - (void)_initFrameObserver {
- UIView *keyboardView = self.keyboardView;
- if (!keyboardView) return;
- __weak typeof(self) _self = self;
- _YYTextKeyboardViewFrameObserver *observer = [_YYTextKeyboardViewFrameObserver observerForView:keyboardView];
- if (!observer) {
- observer = [_YYTextKeyboardViewFrameObserver new];
- observer.notifyBlock = ^(UIView *keyboard) {
- [_self _keyboardFrameChanged:keyboard];
- };
- [observer addToKeyboardView:keyboardView];
- }
- }
- - (void)dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- }
- + (instancetype)defaultManager {
- static YYTextKeyboardManager *mgr = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- if (![UIApplication isAppExtension]) {
- mgr = [[self alloc] _init];
- }
- });
- return mgr;
- }
- + (void)load {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
- [self defaultManager];
- });
- }
- - (void)addObserver:(id<YYTextKeyboardObserver>)observer {
- if (!observer) return;
- [_observers addObject:observer];
- }
- - (void)removeObserver:(id<YYTextKeyboardObserver>)observer {
- if (!observer) return;
- [_observers removeObject:observer];
- }
- - (UIWindow *)keyboardWindow {
- UIApplication *app = [UIApplication sharedExtensionApplication];
- if (!app) return nil;
-
- UIWindow *window = nil;
- for (window in app.windows) {
- if ([self _getKeyboardViewFromWindow:window]) return window;
- }
- window = app.keyWindow;
- if ([self _getKeyboardViewFromWindow:window]) return window;
-
- NSMutableArray *kbWindows = nil;
- for (window in app.windows) {
- NSString *windowName = NSStringFromClass(window.class);
- if ([self _systemVersion] < 9) {
- // UITextEffectsWindow
- if (windowName.length == 19 &&
- [windowName hasPrefix:@"UI"] &&
- [windowName hasSuffix:@"TextEffectsWindow"]) {
- if (!kbWindows) kbWindows = [NSMutableArray new];
- [kbWindows addObject:window];
- }
- } else {
- // UIRemoteKeyboardWindow
- if (windowName.length == 22 &&
- [windowName hasPrefix:@"UI"] &&
- [windowName hasSuffix:@"RemoteKeyboardWindow"]) {
- if (!kbWindows) kbWindows = [NSMutableArray new];
- [kbWindows addObject:window];
- }
- }
- }
-
- if (kbWindows.count == 1) {
- return kbWindows.firstObject;
- }
- return nil;
- }
- - (UIView *)keyboardView {
- UIApplication *app = [UIApplication sharedExtensionApplication];
- if (!app) return nil;
-
- UIWindow *window = nil;
- UIView *view = nil;
- for (window in app.windows) {
- view = [self _getKeyboardViewFromWindow:window];
- if (view) return view;
- }
- window = app.keyWindow;
- view = [self _getKeyboardViewFromWindow:window];
- if (view) return view;
- return nil;
- }
- - (BOOL)isKeyboardVisible {
- UIWindow *window = self.keyboardWindow;
- if (!window) return NO;
- UIView *view = self.keyboardView;
- if (!view) return NO;
- CGRect rect = CGRectIntersection(window.bounds, view.frame);
- if (CGRectIsNull(rect)) return NO;
- if (CGRectIsInfinite(rect)) return NO;
- return rect.size.width > 0 && rect.size.height > 0;
- }
- - (CGRect)keyboardFrame {
- UIView *keyboard = [self keyboardView];
- if (!keyboard) return CGRectNull;
-
- CGRect frame = CGRectNull;
- UIWindow *window = keyboard.window;
- if (window) {
- frame = [window convertRect:keyboard.frame toWindow:nil];
- } else {
- frame = keyboard.frame;
- }
- return frame;
- }
- #pragma mark - private
- - (CGFloat)_systemVersion {
- static CGFloat v;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- v = [UIDevice currentDevice].systemVersion.floatValue;
- });
- return v;
- }
- - (UIView *)_getKeyboardViewFromWindow:(UIWindow *)window {
- /*
- iOS 6/7:
- UITextEffectsWindow
- UIPeripheralHostView << keyboard
-
- iOS 8:
- UITextEffectsWindow
- UIInputSetContainerView
- UIInputSetHostView << keyboard
-
- iOS 9:
- UIRemoteKeyboardWindow
- UIInputSetContainerView
- UIInputSetHostView << keyboard
- */
- if (!window) return nil;
-
- // Get the window
- NSString *windowName = NSStringFromClass(window.class);
- if ([self _systemVersion] < 9) {
- // UITextEffectsWindow
- if (windowName.length != 19) return nil;
- if (![windowName hasPrefix:@"UI"]) return nil;
- if (![windowName hasSuffix:@"TextEffectsWindow"]) return nil;
- } else {
- // UIRemoteKeyboardWindow
- if (windowName.length != 22) return nil;
- if (![windowName hasPrefix:@"UI"]) return nil;
- if (![windowName hasSuffix:@"RemoteKeyboardWindow"]) return nil;
- }
-
- // Get the view
- if ([self _systemVersion] < 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:@"PeripheralHostView"]) 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:@"InputSetContainerView"]) continue;
- // UIInputSetHostView
- for (UIView *subView in view.subviews) {
- NSString *subViewName = NSStringFromClass(subView.class);
- if (subViewName.length != 18) continue;
- if (![subViewName hasPrefix:@"UI"]) continue;
- if (![subViewName hasSuffix:@"InputSetHostView"]) continue;
- return subView;
- }
- }
- }
-
- return nil;
- }
- - (void)_keyboardFrameWillChangeNotification:(NSNotification *)notif {
- if (![notif.name isEqualToString:UIKeyboardWillChangeFrameNotification]) return;
- NSDictionary *info = notif.userInfo;
- if (!info) return;
-
- [self _initFrameObserver];
-
- NSValue *beforeValue = info[UIKeyboardFrameBeginUserInfoKey];
- NSValue *afterValue = info[UIKeyboardFrameEndUserInfoKey];
- NSNumber *curveNumber = info[UIKeyboardAnimationCurveUserInfoKey];
- NSNumber *durationNumber = info[UIKeyboardAnimationDurationUserInfoKey];
-
- CGRect before = beforeValue.CGRectValue;
- CGRect after = afterValue.CGRectValue;
- UIViewAnimationCurve curve = curveNumber.integerValue;
- NSTimeInterval duration = durationNumber.doubleValue;
-
- // ignore zero end frame
- if (after.size.width <= 0 && after.size.height <= 0) return;
-
- _notificationFromFrame = before;
- _notificationToFrame = after;
- _notificationCurve = curve;
- _notificationDuration = duration;
- _hasNotification = YES;
- _lastIsNotification = YES;
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_notifyAllObservers) object:nil];
- if (duration == 0) {
- [self performSelector:@selector(_notifyAllObservers) withObject:nil afterDelay:0 inModes:@[NSRunLoopCommonModes]];
- } else {
- [self _notifyAllObservers];
- }
- }
- - (void)_keyboardFrameDidChangeNotification:(NSNotification *)notif {
- if (![notif.name isEqualToString:UIKeyboardDidChangeFrameNotification]) return;
- NSDictionary *info = notif.userInfo;
- if (!info) return;
-
- [self _initFrameObserver];
-
- NSValue *afterValue = info[UIKeyboardFrameEndUserInfoKey];
- CGRect after = afterValue.CGRectValue;
-
- // ignore zero end frame
- if (after.size.width <= 0 && after.size.height <= 0) return;
-
- _notificationToFrame = after;
- _notificationCurve = UIViewAnimationCurveEaseInOut;
- _notificationDuration = 0;
- _hasNotification = YES;
- _lastIsNotification = YES;
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_notifyAllObservers) object:nil];
- [self performSelector:@selector(_notifyAllObservers) withObject:nil afterDelay:0 inModes:@[NSRunLoopCommonModes]];
- }
- - (void)_keyboardFrameChanged:(UIView *)keyboard {
- if (keyboard != self.keyboardView) return;
-
- UIWindow *window = keyboard.window;
- if (window) {
- _observedToFrame = [window convertRect:keyboard.frame toWindow:nil];
- } else {
- _observedToFrame = keyboard.frame;
- }
- _hasObservedChange = YES;
- _lastIsNotification = NO;
-
- [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_notifyAllObservers) object:nil];
- [self performSelector:@selector(_notifyAllObservers) withObject:nil afterDelay:0 inModes:@[NSRunLoopCommonModes]];
- }
- - (void)_notifyAllObservers {
- UIApplication *app = [UIApplication sharedExtensionApplication];
- if (!app) return;
-
- UIView *keyboard = self.keyboardView;
- UIWindow *window = keyboard.window;
- if (!window) {
- window = app.keyWindow;
- }
- if (!window) {
- window = app.windows.firstObject;
- }
-
- YYTextKeyboardTransition trans = {0};
-
- // from
- if (_fromFrame.size.width == 0 && _fromFrame.size.height == 0) { // first notify
- _fromFrame.size.width = window.bounds.size.width;
- _fromFrame.size.height = trans.toFrame.size.height;
- _fromFrame.origin.x = trans.toFrame.origin.x;
- _fromFrame.origin.y = window.bounds.size.height;
- }
- trans.fromFrame = _fromFrame;
- trans.fromVisible = _fromVisible;
-
- // to
- if (_lastIsNotification || (_hasObservedChange && CGRectEqualToRect(_observedToFrame, _notificationToFrame))) {
- trans.toFrame = _notificationToFrame;
- trans.animationDuration = _notificationDuration;
- trans.animationCurve = _notificationCurve;
- trans.animationOption = _notificationCurve << 16;
-
- // Fix iPad(iOS7) keyboard frame error after rorate device when the keyboard is not docked to bottom.
- if (((int)[self _systemVersion]) == 7) {
- UIInterfaceOrientation ori = app.statusBarOrientation;
- if (_fromOrientation != UIInterfaceOrientationUnknown && _fromOrientation != ori) {
- switch (ori) {
- case UIInterfaceOrientationPortrait: {
- if (CGRectGetMaxY(trans.toFrame) != window.frame.size.height) {
- trans.toFrame.origin.y -= trans.toFrame.size.height;
- }
- } break;
- case UIInterfaceOrientationPortraitUpsideDown: {
- if (CGRectGetMinY(trans.toFrame) != 0) {
- trans.toFrame.origin.y += trans.toFrame.size.height;
- }
- } break;
- case UIInterfaceOrientationLandscapeLeft: {
- if (CGRectGetMaxX(trans.toFrame) != window.frame.size.width) {
- trans.toFrame.origin.x -= trans.toFrame.size.width;
- }
- } break;
- case UIInterfaceOrientationLandscapeRight: {
- if (CGRectGetMinX(trans.toFrame) != 0) {
- trans.toFrame.origin.x += trans.toFrame.size.width;
- }
- } break;
- default: break;
- }
- }
- }
- } else {
- trans.toFrame = _observedToFrame;
- }
-
- if (window && trans.toFrame.size.width > 0 && trans.toFrame.size.height > 0) {
- CGRect rect = CGRectIntersection(window.bounds, trans.toFrame);
- if (!CGRectIsNull(rect) && !CGRectIsEmpty(rect)) {
- trans.toVisible = YES;
- }
- }
-
- if (!CGRectEqualToRect(trans.toFrame, _fromFrame)) {
- for (id<YYTextKeyboardObserver> observer in _observers.copy) {
- if ([observer respondsToSelector:@selector(keyboardChangedWithTransition:)]) {
- [observer keyboardChangedWithTransition:trans];
- }
- }
- }
-
- _hasNotification = NO;
- _hasObservedChange = NO;
- _fromFrame = trans.toFrame;
- _fromVisible = trans.toVisible;
- _fromOrientation = app.statusBarOrientation;
- }
- - (CGRect)convertRect:(CGRect)rect toView:(UIView *)view {
- UIApplication *app = [UIApplication sharedExtensionApplication];
- if (!app) return CGRectZero;
-
- if (CGRectIsNull(rect)) return rect;
- if (CGRectIsInfinite(rect)) return rect;
-
- UIWindow *mainWindow = app.keyWindow;
- if (!mainWindow) mainWindow = app.windows.firstObject;
- if (!mainWindow) { // no window ?!
- if (view) {
- [view convertRect:rect fromView:nil];
- } else {
- return rect;
- }
- }
-
- rect = [mainWindow convertRect:rect fromWindow:nil];
- if (!view) return [mainWindow convertRect:rect toWindow:nil];
- if (view == mainWindow) return rect;
-
- UIWindow *toWindow = [view isKindOfClass:[UIWindow class]] ? (id)view : view.window;
- if (!mainWindow || !toWindow) return [mainWindow convertRect:rect toView:view];
- if (mainWindow == toWindow) return [mainWindow convertRect:rect toView:view];
-
- // in different window
- rect = [mainWindow convertRect:rect toView:mainWindow];
- rect = [toWindow convertRect:rect fromWindow:mainWindow];
- rect = [view convertRect:rect fromView:toWindow];
- return rect;
- }
- @end
|