diff --git a/QMUIKit.podspec b/QMUIKit.podspec index 20a5aac6..39c55065 100644 --- a/QMUIKit.podspec +++ b/QMUIKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "QMUIKit" - s.version = "2.7.0" + s.version = "2.7.1" s.summary = "致力于提高项目 UI 开发效率的解决方案" s.description = <<-DESC QMUI iOS 是一个致力于提高项目 UI 开发效率的解决方案,其设计目的是用于辅助快速搭建一个具备基本设计还原效果的 iOS 项目,同时利用自身提供的丰富控件及兼容处理, 让开发者能专注于业务需求而无需耗费精力在基础代码的设计上。不管是新项目的创建,或是已有项目的维护,均可使开发效率和项目质量得到大幅度提升。 diff --git a/QMUIKit/Info.plist b/QMUIKit/Info.plist index 5c555d18..e46a403d 100644 --- a/QMUIKit/Info.plist +++ b/QMUIKit/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.7.0 + 2.7.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSPrincipalClass diff --git a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m index e274e33b..488dc552 100644 --- a/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m +++ b/QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m @@ -125,6 +125,10 @@ - (void)viewDidLayoutSubviews { } } +- (BOOL)prefersStatusBarHidden { + return YES; +} + - (void)setToolBarBackgroundColor:(UIColor *)toolBarBackgroundColor { _toolBarBackgroundColor = toolBarBackgroundColor; self.topToolBarView.backgroundColor = self.toolBarBackgroundColor; diff --git a/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.h b/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.h index 4f658a56..0366fc06 100644 --- a/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.h +++ b/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.h @@ -21,8 +21,11 @@ #pragma mark - Badge -/// 设置未读数,0 则隐藏未读数 -@property(nonatomic, assign) NSUInteger qmui_badgeValue; +/// 用数字设置未读数,0表示不显示未读数 +@property(nonatomic, assign) NSUInteger qmui_badgeInteger; + +/// 用字符串设置未读数,nil 表示不显示未读数 +@property(nonatomic, copy) NSString *qmui_badgeString; @property(nonatomic, strong, nullable) UIColor *qmui_badgeBackgroundColor; @property(nonatomic, strong, nullable) UIColor *qmui_badgeTextColor; @@ -44,6 +47,7 @@ #pragma mark - UpdatesIndicator +/// 控制红点的显隐 @property(nonatomic, assign) BOOL qmui_shouldShowUpdatesIndicator; @property(nonatomic, strong, nullable) UIColor *qmui_updatesIndicatorColor; @property(nonatomic, assign) CGSize qmui_updatesIndicatorSize; diff --git a/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m b/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m index c9daa81d..0a97b929 100644 --- a/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m +++ b/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m @@ -46,7 +46,7 @@ + (void)load { // 针对非 customView 的 UIBarButtonItem,负责将红点添加上去 ExtendImplementationOfVoidMethodWithSingleArgument([UIBarButtonItem class], @selector(setView:), UIView *, ^(UIBarButtonItem *selfObject, UIView *firstArgv) { - if (selfObject.qmui_badgeValue > 0 && selfObject.qmui_badgeLabel) { + if (selfObject.qmui_badgeString.length && selfObject.qmui_badgeLabel) { [firstArgv addSubview:selfObject.qmui_badgeLabel]; } if (selfObject.qmui_shouldShowUpdatesIndicator && selfObject.qmui_updatesIndicatorView) { @@ -56,7 +56,7 @@ + (void)load { // 针对 UITabBarItem,负责将红点添加上去 ExtendImplementationOfVoidMethodWithSingleArgument([UITabBarItem class], @selector(setView:), UIView *, ^(UITabBarItem *selfObject, UIView *firstArgv) { - if (selfObject.qmui_badgeValue > 0 && selfObject.qmui_badgeLabel) { + if (selfObject.qmui_badgeString.length && selfObject.qmui_badgeLabel) { [firstArgv addSubview:selfObject.qmui_badgeLabel]; } if (selfObject.qmui_shouldShowUpdatesIndicator && selfObject.qmui_updatesIndicatorView) { @@ -113,10 +113,20 @@ - (void)didInitialize { #pragma mark - Badge -static char kAssociatedObjectKey_badgeValue; -- (void)setQmui_badgeValue:(NSUInteger)qmui_badgeValue { - objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeValue, @(qmui_badgeValue), OBJC_ASSOCIATION_RETAIN_NONATOMIC); - if (qmui_badgeValue > 0) { +static char kAssociatedObjectKey_badgeInteger; +- (void)setQmui_badgeInteger:(NSUInteger)qmui_badgeInteger { + objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeInteger, @(qmui_badgeInteger), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + self.qmui_badgeString = qmui_badgeInteger > 0 ? [NSString stringWithFormat:@"%@", @(qmui_badgeInteger)] : nil; +} + +- (NSUInteger)qmui_badgeInteger { + return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_badgeInteger)) unsignedIntegerValue]; +} + +static char kAssociatedObjectKey_badgeString; +- (void)setQmui_badgeString:(NSString *)qmui_badgeString { + objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeString, qmui_badgeString, OBJC_ASSOCIATION_COPY_NONATOMIC); + if (qmui_badgeString.length) { if (!self.qmui_badgeLabel) { self.qmui_badgeLabel = [[_QMUIBadgeLabel alloc] init]; self.qmui_badgeLabel.clipsToBounds = YES; @@ -129,7 +139,7 @@ - (void)setQmui_badgeValue:(NSUInteger)qmui_badgeValue { self.qmui_badgeLabel.centerOffsetLandscape = self.qmui_badgeCenterOffsetLandscape; [self.qmui_view addSubview:self.qmui_badgeLabel]; } - self.qmui_badgeLabel.text = [NSString stringWithFormat:@"%@", @(qmui_badgeValue)]; + self.qmui_badgeLabel.text = qmui_badgeString; self.qmui_badgeLabel.hidden = NO; [self setNeedsUpdateBadgeLabelLayout]; } else { @@ -137,8 +147,8 @@ - (void)setQmui_badgeValue:(NSUInteger)qmui_badgeValue { } } -- (NSUInteger)qmui_badgeValue { - return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_badgeValue)) unsignedIntegerValue]; +- (NSString *)qmui_badgeString { + return (NSString *)objc_getAssociatedObject(self, &kAssociatedObjectKey_badgeString); } static char kAssociatedObjectKey_badgeBackgroundColor; @@ -225,7 +235,7 @@ - (_QMUIBadgeLabel *)qmui_badgeLabel { } - (void)setNeedsUpdateBadgeLabelLayout { - if (self.qmui_badgeValue > 0) { + if (self.qmui_badgeString.length) { if ([self isKindOfClass:[UIBarButtonItem class]] && ((UIBarButtonItem *)self).customView) { // 如果是 customView,由于无法重写它的 layoutSubviews,所以认为它目前的 frame 已经是最终的 frame,直接按照当前 frame 来布局即可 [self.qmui_badgeLabel updateLayout]; diff --git a/QMUIKit/QMUIComponents/QMUICellHeightCache.h b/QMUIKit/QMUIComponents/QMUICellHeightCache.h index 081b000a..5b33896f 100644 --- a/QMUIKit/QMUIComponents/QMUICellHeightCache.h +++ b/QMUIKit/QMUIComponents/QMUICellHeightCache.h @@ -40,7 +40,7 @@ - (void)invalidateAllHeightCache; // 给 tableview 和 collectionview 调用的方法 -- (void)enumerateAllOrientationsUsingBlock:(void (^)(NSMutableArray *heightsBySection))block; +- (void)enumerateAllOrientationsUsingBlock:(void (^)(NSMutableArray *> *heightsBySection))block; - (void)buildSectionsIfNeeded:(NSInteger)targetSection; - (void)buildCachesAtIndexPathsIfNeeded:(NSArray *)indexPaths; diff --git a/QMUIKit/QMUIComponents/QMUICellHeightCache.m b/QMUIKit/QMUIComponents/QMUICellHeightCache.m index 22b78c6d..3af64739 100644 --- a/QMUIKit/QMUIComponents/QMUICellHeightCache.m +++ b/QMUIKit/QMUIComponents/QMUICellHeightCache.m @@ -11,10 +11,11 @@ #import "QMUICore.h" #import "UIScrollView+QMUI.h" #import "UIView+QMUI.h" +#import "NSNumber+QMUI.h" @implementation QMUICellHeightCache { - NSMutableDictionary *_mutableHeightsByKeyForPortrait; - NSMutableDictionary *_mutableHeightsByKeyForLandscape; + NSMutableDictionary, NSNumber *> *_mutableHeightsByKeyForPortrait; + NSMutableDictionary, NSNumber *> *_mutableHeightsByKeyForLandscape; } - (instancetype)init { @@ -26,7 +27,7 @@ - (instancetype)init { return self; } -- (NSMutableDictionary *)mutableHeightsByKeyForCurrentOrientation { +- (NSMutableDictionary, NSNumber *> *)mutableHeightsByKeyForCurrentOrientation { return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? _mutableHeightsByKeyForPortrait : _mutableHeightsByKeyForLandscape; } @@ -40,11 +41,7 @@ - (void)cacheHeight:(CGFloat)height byKey:(id)key { } - (CGFloat)heightForKey:(id)key { -#if CGFLOAT_IS_DOUBLE - return [[self mutableHeightsByKeyForCurrentOrientation][key] doubleValue]; -#else - return [[self mutableHeightsByKeyForCurrentOrientation][key] floatValue]; -#endif + return [self mutableHeightsByKeyForCurrentOrientation][key].qmui_CGFloatValue; } - (void)invalidateHeightForKey:(id)key { @@ -64,8 +61,8 @@ - (NSString *)description { @end @implementation QMUICellHeightIndexPathCache { - NSMutableArray *_heightsBySectionForPortrait; - NSMutableArray *_heightsBySectionForLandscape; + NSMutableArray *> *_heightsBySectionForPortrait; + NSMutableArray *> *_heightsBySectionForLandscape; } - (instancetype)init { @@ -77,11 +74,11 @@ - (instancetype)init { return self; } -- (NSMutableArray *)heightsBySectionForCurrentOrientation { +- (NSMutableArray *> *)heightsBySectionForCurrentOrientation { return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? _heightsBySectionForPortrait : _heightsBySectionForLandscape; } -- (void)enumerateAllOrientationsUsingBlock:(void (^)(NSMutableArray *heightsBySection))block { +- (void)enumerateAllOrientationsUsingBlock:(void (^)(NSMutableArray *> *heightsBySection))block { if (block) { block(_heightsBySectionForPortrait); block(_heightsBySectionForLandscape); @@ -102,28 +99,23 @@ - (void)cacheHeight:(CGFloat)height byIndexPath:(NSIndexPath *)indexPath { - (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath { [self buildCachesAtIndexPathsIfNeeded:@[indexPath]]; - NSNumber *number = self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row]; -#if CGFLOAT_IS_DOUBLE - return number.doubleValue; -#else - return number.floatValue; -#endif + return self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row].qmui_CGFloatValue; } - (void)invalidateHeightAtIndexPath:(NSIndexPath *)indexPath { [self buildCachesAtIndexPathsIfNeeded:@[indexPath]]; - [self enumerateAllOrientationsUsingBlock:^(NSMutableArray *heightsBySection) { + [self enumerateAllOrientationsUsingBlock:^(NSMutableArray *> *heightsBySection) { heightsBySection[indexPath.section][indexPath.row] = @-1; }]; } - (void)invalidateAllHeightCache { - [self enumerateAllOrientationsUsingBlock:^(NSMutableArray *heightsBySection) { + [self enumerateAllOrientationsUsingBlock:^(NSMutableArray *> *heightsBySection) { [heightsBySection removeAllObjects]; }]; } -- (void)buildCachesAtIndexPathsIfNeeded:(NSArray *)indexPaths { +- (void)buildCachesAtIndexPathsIfNeeded:(NSArray *)indexPaths { [indexPaths enumerateObjectsUsingBlock:^(NSIndexPath *indexPath, NSUInteger idx, BOOL *stop) { [self buildSectionsIfNeeded:indexPath.section]; [self buildRowsIfNeeded:indexPath.row inExistSection:indexPath.section]; @@ -131,7 +123,7 @@ - (void)buildCachesAtIndexPathsIfNeeded:(NSArray *)indexPaths { } - (void)buildSectionsIfNeeded:(NSInteger)targetSection { - [self enumerateAllOrientationsUsingBlock:^(NSMutableArray *heightsBySection) { + [self enumerateAllOrientationsUsingBlock:^(NSMutableArray *> *heightsBySection) { for (NSInteger section = 0; section <= targetSection; ++section) { if (section >= heightsBySection.count) { heightsBySection[section] = [NSMutableArray array]; @@ -141,7 +133,7 @@ - (void)buildSectionsIfNeeded:(NSInteger)targetSection { } - (void)buildRowsIfNeeded:(NSInteger)targetRow inExistSection:(NSInteger)section { - [self enumerateAllOrientationsUsingBlock:^(NSMutableArray *heightsBySection) { + [self enumerateAllOrientationsUsingBlock:^(NSMutableArray *> *heightsBySection) { NSMutableArray *heightsByRow = heightsBySection[section]; for (NSInteger row = 0; row <= targetRow; ++row) { if (row >= heightsByRow.count) { diff --git a/QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.h b/QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.h index d3c1b60f..9883df83 100644 --- a/QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.h +++ b/QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.h @@ -16,7 +16,8 @@ * 1. 将 tableView.qmui_cacheCellHeightByKeyAutomatically = YES * 2. 实现 tableView 的 delegate 方法 qmui_tableView:cacheKeyForRowAtIndexPath: 返回一个 key。建议 key 由所有可能影响高度的字段拼起来,这样当数据发生变化时不需要手动更新缓存。 * - * @note 注意这里的高度缓存仅适合于使用 self-sizing 机制的 tableView(也即 tableView.rowHeight = UITableViewAutomaticDimension),QMUICellHeightKeyCache 会自动在 willDisplayCell 里将 cell 的当前高度缓存起来,然后在 heightForRow 里从缓存中读取高度后使用。而如果你的 tableView 并没有使用 self-sizing 机制(也即自己重写了 heightForRow),则请勿使用本控件的功能。 + * @note 注意这里的高度缓存仅适合于使用 self-sizing 机制的 tableView(也即 tableView.rowHeight = UITableViewAutomaticDimension),QMUICellHeightKeyCache 会自动在 willDisplayCell 里将 cell 的当前高度缓存起来,然后在 heightForRow 里从缓存中读取高度后使用。 + * @note 如果 tableView.delegate 指向的类既想使用 QMUICellHeightKeyCache 的功能,又需要在 tableView:heightForRowAtIndexPath: 里写业务逻辑,则可通过 tableView:heightForRowAtIndexPath: 返回 -1 来使用 QMUICellHeightKeyCache 的计算结果,返回大于等于 0 的值将不会触发 QMUICellHeightKeyCache 的计算,具体请看 QMUI Demo。 * * @note 在 UITableView 的宽度和 contentInset 发生变化时(例如横竖屏旋转、iPad 分屏),高度缓存会自动刷新,所以无需为这种情况做保护。 */ diff --git a/QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.m b/QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.m index 95cfe31b..0a0ca483 100644 --- a/QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.m +++ b/QMUIKit/QMUIComponents/QMUICellHeightKeyCache/UITableView+QMUICellHeightKeyCache.m @@ -44,13 +44,13 @@ - (void)setQmui_cacheCellHeightByKeyAutomatically:(BOOL)qmui_cacheCellHeightByKe NSAssert(!self.delegate || [self.delegate respondsToSelector:@selector(qmui_tableView:cacheKeyForRowAtIndexPath:)], @"%@ 需要实现 %@ 方法才能自动缓存 cell 高度", self.delegate, NSStringFromSelector(@selector(qmui_tableView:cacheKeyForRowAtIndexPath:))); NSAssert(self.estimatedRowHeight != 0, @"estimatedRowHeight 不能为 0,否则无法开启 self-sizing cells 功能"); - [self replaceMethodForDelegateIfNeeded:self.delegate]; + [self replaceMethodForDelegateIfNeeded:(id)self.delegate]; // 在上面那一句 replaceMethodForDelegateIfNeeded 里可能修改了 delegate 里的一些方法,所以需要通过重新设置 delegate 来触发 tableView 读取新的方法。iOS 8 要先置空再设置才能生效。 if (@available(iOS 9.0, *)) { self.delegate = self.delegate; } else { - id tempDelegate = self.delegate; + id tempDelegate = (id)self.delegate; self.delegate = nil; self.delegate = tempDelegate; } @@ -93,35 +93,33 @@ - (CGFloat)widthForCacheKey { } - (void)qmui_tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { - [tableView qmui_tableView:tableView willDisplayCell:cell forRowAtIndexPath:indexPath]; if (tableView.qmui_cacheCellHeightByKeyAutomatically) { - id cachedKey = [((id)self) qmui_tableView:tableView cacheKeyForRowAtIndexPath:indexPath]; + id cachedKey = [((id)tableView.delegate) qmui_tableView:tableView cacheKeyForRowAtIndexPath:indexPath]; [tableView.qmui_currentCellHeightKeyCache cacheHeight:CGRectGetHeight(cell.frame) forKey:cachedKey]; } } - (CGFloat)qmui_tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if (tableView.qmui_cacheCellHeightByKeyAutomatically) { - id cachedKey = [((id)self) qmui_tableView:tableView cacheKeyForRowAtIndexPath:indexPath]; + id cachedKey = [((id)tableView.delegate) qmui_tableView:tableView cacheKeyForRowAtIndexPath:indexPath]; if ([tableView.qmui_currentCellHeightKeyCache existsHeightForKey:cachedKey]) { return [tableView.qmui_currentCellHeightKeyCache heightForKey:cachedKey]; } // 由于 QMUICellHeightKeyCache 只对 self-sizing 的 cell 生效,所以这里返回这个值,以使用 self-sizing 效果 return UITableViewAutomaticDimension; } else { - // 对于开启过 qmui_cacheCellHeightByKeyAutomatically 然后又关闭的 class 就会走到这里,此时已经无法调用回之前被替换的方法的实现,所以直接使用 tableView.rowHeight - // TODO: molice 最好应该在 replaceMethodForDelegateIfNeeded: 里判断在替换方法之前 delegate 是否已经有实现 heightForRow,如果有,则在这里调用回它自己的实现,如果没有,再使用 tableView.rowHeight,不然现在的做法会导致 delegate 里关闭了自动缓存的情况下就算实现了 heightForRow,也无法被调用。 + // 对于开启过 qmui_cacheCellHeightByKeyAutomatically 然后又关闭的 class 就会走到这里,做个保护而已。理论上走到这个分支本身就是没有意义的。 return tableView.rowHeight; } } -- (void)qmui_setDelegate:(id)delegate { +- (void)qmui_setDelegate:(id)delegate { [self replaceMethodForDelegateIfNeeded:delegate]; [self qmui_setDelegate:delegate]; } static NSMutableSet *qmui_methodsReplacedClasses; -- (void)replaceMethodForDelegateIfNeeded:(id)delegate { +- (void)replaceMethodForDelegateIfNeeded:(id)delegate { if (self.qmui_cacheCellHeightByKeyAutomatically && delegate) { if (!qmui_methodsReplacedClasses) { qmui_methodsReplacedClasses = [NSMutableSet set]; @@ -131,8 +129,71 @@ - (void)replaceMethodForDelegateIfNeeded:(id)delegate { } [qmui_methodsReplacedClasses addObject:NSStringFromClass(delegate.class)]; - ExchangeImplementationsInTwoClasses(delegate.class, @selector(tableView:willDisplayCell:forRowAtIndexPath:), self.class, @selector(qmui_tableView:willDisplayCell:forRowAtIndexPath:)); - ExchangeImplementationsInTwoClasses(delegate.class, @selector(tableView:heightForRowAtIndexPath:), self.class, @selector(qmui_tableView:heightForRowAtIndexPath:)); + [self handleWillDisplayCellMethodForDelegate:delegate]; + [self handleHeightForRowMethodForDelegate:delegate]; + + } +} + +- (void)handleWillDisplayCellMethodForDelegate:(id)delegate { + // 如果 delegate 本身没有实现 tableView:willDisplayCell:forRowAtIndexPath:,则为它添加一个。 + // 如果 delegate 已经有实现,则在调用完 delegate 自身的实现后,再调用我们自己的实现去存储计算后的 cell 高度 + SEL willDisplayCellSelector = @selector(tableView:willDisplayCell:forRowAtIndexPath:); + Method willDisplayCellMethod = class_getInstanceMethod([self class], @selector(qmui_tableView:willDisplayCell:forRowAtIndexPath:)); + IMP willDisplayCellIMP = method_getImplementation(willDisplayCellMethod); + void (*willDisplayCellFunction)(id, SEL, UITableView *, UITableViewCell *, NSIndexPath *); + willDisplayCellFunction = (void (*)(id, SEL, UITableView *, UITableViewCell *, NSIndexPath *))willDisplayCellIMP; + + BOOL addedSuccessfully = class_addMethod(delegate.class, willDisplayCellSelector, willDisplayCellIMP, method_getTypeEncoding(willDisplayCellMethod)); + if (!addedSuccessfully) { + OverrideImplementation([delegate class], willDisplayCellSelector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP originIMP) { + return ^(id delegateSelf, UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) { + + // call super + void (*originSelectorIMP)(id, SEL, UITableView *, UITableViewCell *, NSIndexPath *); + originSelectorIMP = (void (*)(id, SEL, UITableView *, UITableViewCell *, NSIndexPath *))originIMP; + originSelectorIMP(delegateSelf, originCMD, tableView, cell, indexPath); + + // avoid superclass + if (![delegateSelf isKindOfClass:originClass]) return; + + // call QMUI + willDisplayCellFunction(delegateSelf, willDisplayCellSelector, tableView, cell, indexPath); + }; + }); + } +} + +- (void)handleHeightForRowMethodForDelegate:(id)delegate { + // 如果 delegate 本身没有实现 tableView:heightForRowAtIndexPath:,则为它添加一个。 + // 如果 delegate 已经有实现,则优先拿它的实现的值来 return,如果它的值小于0(例如-1),则认为它想用 QMUICellHeightKeyCache 的计算,此时再 return 我们自己的计算结果 + SEL heightForRowSelector = @selector(tableView:heightForRowAtIndexPath:); + Method heightForRowMethod = class_getInstanceMethod([self class], @selector(qmui_tableView:heightForRowAtIndexPath:)); + IMP heightForRowIMP = method_getImplementation(heightForRowMethod); + CGFloat (*heightForRowFunction)(id, SEL, UITableView *, NSIndexPath *); + heightForRowFunction = (CGFloat (*)(id, SEL, UITableView *, NSIndexPath *))heightForRowIMP; + + BOOL addedSuccessfully = class_addMethod([delegate class], heightForRowSelector, heightForRowIMP, method_getTypeEncoding(heightForRowMethod)); + if (!addedSuccessfully) { + OverrideImplementation([delegate class], heightForRowSelector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP originIMP) { + return ^CGFloat(id delegateSelf, UITableView *tableView, NSIndexPath *indexPath) { + + // call super + CGFloat (*originSelectorIMP)(id, SEL, UITableView *, NSIndexPath *); + originSelectorIMP = (CGFloat (*)(id, SEL, UITableView *, NSIndexPath *))originIMP; + CGFloat result = originSelectorIMP(delegateSelf, originCMD, tableView, indexPath); + + // avoid superclass + if (![delegateSelf isKindOfClass:originClass]) return result; + + if (result >= 0) { + return result; + } + + // call QMUI + return heightForRowFunction(delegateSelf, heightForRowSelector, tableView, indexPath); + }; + }); } } diff --git a/QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.h b/QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.h index 85d93627..cab072c7 100644 --- a/QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.h +++ b/QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.h @@ -17,6 +17,7 @@ - (nonnull id)qmui_collectionView:(nonnull UICollectionView *)collectionView cacheKeyForItemAtIndexPath:(nonnull NSIndexPath *)indexPath; @end +/// 注意,这个类的功能暂无法使用 @interface UICollectionView (QMUICellSizeKeyCache) /// 控制是否要自动缓存 cell 的高度,默认为 NO diff --git a/QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.m b/QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.m index 4730ea2b..4a12a1eb 100644 --- a/QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.m +++ b/QMUIKit/QMUIComponents/QMUICellSizeKeyCache/UICollectionView+QMUICellSizeKeyCache.m @@ -182,9 +182,6 @@ - (void)replaceMethodForDelegateIfNeeded:(id)delegate return; } [qmui_methodsReplacedClasses addObject:NSStringFromClass(delegate.class)]; - - ExchangeImplementationsInTwoClasses(delegate.class, @selector(collectionView:willDisplayCell:forItemAtIndexPath:), self.class, @selector(qmui_collectionView:willDisplayCell:forItemAtIndexPath:)); -// ExchangeImplementationsInTwoClasses(delegate.class, @selector(collectionView:layout:sizeForItemAtIndexPath:), self.class, @selector(qmui_collectionView:layout:sizeForItemAtIndexPath:)); } } diff --git a/QMUIKit/QMUIComponents/QMUIDialogViewController.m b/QMUIKit/QMUIComponents/QMUIDialogViewController.m index a16c887a..45bcb421 100644 --- a/QMUIKit/QMUIComponents/QMUIDialogViewController.m +++ b/QMUIKit/QMUIComponents/QMUIDialogViewController.m @@ -401,7 +401,7 @@ - (void)hideWithAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { #pragma mark - -- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller limitSize:(CGSize)limitSize { +- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller keyboardHeight:(CGFloat)keyboardHeight limitSize:(CGSize)limitSize { if (!self.hasCustomContentView) { return limitSize; } @@ -601,7 +601,7 @@ - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath #pragma mark - -- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller limitSize:(CGSize)limitSize { +- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller keyboardHeight:(CGFloat)keyboardHeight limitSize:(CGSize)limitSize { CGFloat contentViewVerticalMargin = UIEdgeInsetsGetVerticalValue(self.contentViewMargins); CGFloat footerHeight = !self.footerView.hidden ? CGRectGetHeight(self.footerView.frame) : 0; CGFloat tableViewLimitHeight = limitSize.height - CGRectGetHeight(self.headerView.frame) - footerHeight - contentViewVerticalMargin; @@ -745,7 +745,7 @@ - (void)addSubmitButtonWithText:(NSString *)buttonText block:(void (^)(__kindof #pragma mark - -- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller limitSize:(CGSize)limitSize { +- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller keyboardHeight:(CGFloat)keyboardHeight limitSize:(CGSize)limitSize { CGFloat textFieldHeight = self.textFieldLabel.hidden ? 56.0 : 25.0; // 25.0 考虑了行高导致的 offsetoffset CGFloat textFieldTitleHeight = 29.0; diff --git a/QMUIKit/QMUIComponents/QMUIEmptyView.m b/QMUIKit/QMUIComponents/QMUIEmptyView.m index 440b6ca9..a71bcd7d 100644 --- a/QMUIKit/QMUIComponents/QMUIEmptyView.m +++ b/QMUIKit/QMUIComponents/QMUIEmptyView.m @@ -57,7 +57,7 @@ - (void)didInitialize { self.scrollView.showsVerticalScrollIndicator = NO; self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.scrollsToTop = NO; - self.scrollView.contentInset = UIEdgeInsetsMake(0, 10, 0, 10); // 避免 label 直接撑满到屏幕两边,不好看 + self.scrollView.contentInset = UIEdgeInsetsMake(0, 16, 0, 16); [self addSubview:self.scrollView]; _contentView = [[UIView alloc] init]; diff --git a/QMUIKit/QMUIComponents/QMUIKeyboardManager.h b/QMUIKit/QMUIComponents/QMUIKeyboardManager.h index 515f4446..b6747d1f 100644 --- a/QMUIKit/QMUIComponents/QMUIKeyboardManager.h +++ b/QMUIKit/QMUIComponents/QMUIKeyboardManager.h @@ -68,7 +68,7 @@ + (CGRect)convertKeyboardRect:(CGRect)rect toView:(UIView *)view; /** - * 获取键盘到顶部到相对于view底部的距离,这个值在某些情况下会等于endFrame.size.height或者visiableKeyboardHeight,不过在iPad浮动键盘的时候就包括了底部的空隙。所以建议使用这个方法。 + * 获取键盘到顶部到相对于view底部的距离,这个值在某些情况下会等于endFrame.size.height或者visibleKeyboardHeight,不过在iPad浮动键盘的时候就包括了底部的空隙。所以建议使用这个方法。 */ + (CGFloat)distanceFromMinYToBottomInView:(UIView *)view keyboardRect:(CGRect)rect; @@ -107,7 +107,7 @@ /** * 当前键盘高度键盘的可见高度 */ -+ (CGFloat)visiableKeyboardHeight; ++ (CGFloat)visibleKeyboardHeight; @end diff --git a/QMUIKit/QMUIComponents/QMUIKeyboardManager.m b/QMUIKit/QMUIComponents/QMUIKeyboardManager.m index 6bbf968f..f4e51797 100644 --- a/QMUIKit/QMUIComponents/QMUIKeyboardManager.m +++ b/QMUIKit/QMUIComponents/QMUIKeyboardManager.m @@ -10,13 +10,15 @@ #import "QMUICore.h" #import "QMUILog.h" +// iOS 8 下当键盘已经升起的时候再聚焦另一个输入框,此时系统不会再发出键盘通知,导致一些逻辑不准确,这里修复系统这个 bug。iOS 9 及以后没问题。 +// 对应的 issue:https://github.com/QMUI/QMUI_iOS/issues/348 +static QMUIKeyboardManager *kKeyboardManagerInstance; @interface QMUIKeyboardManager () @property(nonatomic, strong) NSMutableArray *targetResponderValues; -@property(nonatomic, strong) QMUIKeyboardUserInfo *keyboardMoveUserInfo; -@property(nonatomic, assign) CGRect keyboardMoveBeginRect; +@property(nonatomic, strong) QMUIKeyboardUserInfo *lastUserInfo; @property(nonatomic, weak) UIResponder *currentResponder; @property(nonatomic, weak) UIResponder *currentResponderWhenResign; @@ -60,6 +62,26 @@ + (void)load { - (BOOL)keyboardManager_becomeFirstResponder { self.keyboardManager_isFirstResponder = YES; + if (@available(iOS 9.0, *)) { + return [self keyboardManager_becomeFirstResponder]; + } + + // iOS 8 下如果键盘已经在显示的时候,另一个输入框被聚焦,升起键盘,此时系统不会再发键盘事件给你,但 iOS 9 及以后会发送,所以这里主动给输入框发送键盘事件 + // 对应这个 issue:https://github.com/QMUI/QMUI_iOS/issues/348 + BOOL isTextInputComponents = [self isKindOfClass:[UITextField class]] || [self isKindOfClass:[UITextView class]]; + BOOL isAlreadyFirstResponder = self.isFirstResponder; + BOOL isKeyboardVisible = [QMUIKeyboardManager isKeyboardVisible]; + if (isTextInputComponents && !isAlreadyFirstResponder && isKeyboardVisible) { + BOOL result = [self keyboardManager_becomeFirstResponder]; + if (result) { + NSDictionary *userInfo = kKeyboardManagerInstance.lastUserInfo.notification.userInfo; + [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillChangeFrameNotification object:self userInfo:userInfo]; + [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification object:self userInfo:userInfo]; + [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardDidChangeFrameNotification object:self userInfo:userInfo]; + [[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardDidShowNotification object:self userInfo:userInfo]; + } + return result; + } return [self keyboardManager_becomeFirstResponder]; } @@ -148,11 +170,11 @@ - (CGFloat)heightInView:(UIView *)view { return [self height]; } CGRect keyboardRect = [QMUIKeyboardManager convertKeyboardRect:_endFrame toView:view]; - CGRect visiableRect = CGRectIntersection(view.bounds, keyboardRect); - if (CGRectIsNull(visiableRect)) { + CGRect visibleRect = CGRectIntersection(view.bounds, keyboardRect); + if (CGRectIsNull(visibleRect)) { return 0; } - return visiableRect.size.height; + return visibleRect.size.height; } - (CGRect)beginFrame { @@ -178,105 +200,33 @@ - (UIViewAnimationOptions)animationOptions { @end -@interface QMUIKeyboardViewFrameObserver : NSObject - -@property(nonatomic, copy) void (^keyboardViewChangeFrameBlock)(UIView *keyboardView); +/** + 1. 系统键盘app启动第一次使用键盘的时候,会调用两轮键盘通知事件,之后就只会调用一次。而搜狗等第三方输入法的键盘,目前发现每次都会调用三次键盘通知事件。总之,键盘的通知事件是不确定的。 -- (void)addToKeyboardView:(UIView *)keyboardView; -+ (instancetype)observerForView:(UIView *)keyboardView; + 2. 搜狗键盘可以修改键盘的高度,在修改键盘高度之后,会调用键盘的keyboardWillChangeFrameNotification和keyboardWillShowNotification通知。 -@end + 3. 如果从一个聚焦的输入框直接聚焦到另一个输入框,会调用前一个输入框的keyboardWillChangeFrameNotification,在调用后一个输入框的keyboardWillChangeFrameNotification,最后调用后一个输入框的keyboardWillShowNotification(如果此时是浮动键盘,那么后一个输入框的keyboardWillShowNotification不会被调用;)。 -static char kAssociatedObjectKey_KeyboardViewFrameObserver; + 4. iPad可以变成浮动键盘,固定->浮动:会调用keyboardWillChangeFrameNotification和keyboardWillHideNotification;浮动->固定:会调用keyboardWillChangeFrameNotification和keyboardWillShowNotification;浮动键盘在移动的时候只会调用keyboardWillChangeFrameNotification通知,并且endFrame为zero,fromFrame不为zero,而是移动前键盘的frame。浮动键盘在聚焦和失焦的时候只会调用keyboardWillChangeFrameNotification,不会调用show和hide的notification。 -@implementation QMUIKeyboardViewFrameObserver { - __unsafe_unretained UIView *_keyboardView; -} + 5. iPad可以拆分为左右的小键盘,小键盘的通知具体基本跟浮动键盘一样。 -- (void)addToKeyboardView:(UIView *)keyboardView { - if (_keyboardView == keyboardView) { - return; - } - if (_keyboardView) { - [self removeFrameObserver]; - objc_setAssociatedObject(_keyboardView, &kAssociatedObjectKey_KeyboardViewFrameObserver, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); - } - _keyboardView = keyboardView; - if (keyboardView) { - [self addFrameObserver]; - } - objc_setAssociatedObject(keyboardView, &kAssociatedObjectKey_KeyboardViewFrameObserver, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -} - -- (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)removeFrameObserver { - [_keyboardView removeObserver:self forKeyPath:@"frame"]; - [_keyboardView removeObserver:self forKeyPath:@"center"]; - [_keyboardView removeObserver:self forKeyPath:@"bounds"]; - [_keyboardView removeObserver:self forKeyPath:@"transform"]; - _keyboardView = nil; -} - -- (void)dealloc { - [self removeFrameObserver]; -} - -+ (instancetype)observerForView:(UIView *)keyboardView { - if (!keyboardView) { - return nil; - } - return objc_getAssociatedObject(keyboardView, &kAssociatedObjectKey_KeyboardViewFrameObserver); -} - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if (![keyPath isEqualToString:@"frame"] && - ![keyPath isEqualToString:@"center"] && - ![keyPath isEqualToString:@"bounds"] && - ![keyPath isEqualToString:@"transform"]) { - return; - } - if ([[change objectForKey:NSKeyValueChangeNotificationIsPriorKey] boolValue]) { - return; - } - if ([[change objectForKey:NSKeyValueChangeKindKey] integerValue] != NSKeyValueChangeSetting) { - return; - } - id newValue = [change objectForKey:NSKeyValueChangeNewKey]; - if (newValue == [NSNull null]) { newValue = nil; } - if (self.keyboardViewChangeFrameBlock) { - self.keyboardViewChangeFrameBlock(_keyboardView); - } -} - -@end + 6. iPad可以外接键盘,外接键盘之后屏幕上就没有虚拟键盘了,但是当我们输入文字的时候,发现底部还是有一条灰色的候选词,条东西也是键盘,它也会触发跟虚拟键盘一样的通知事件。如果点击这条候选词右边的向下箭头,则可以完全隐藏虚拟键盘,这个时候如果失焦再聚焦发现还是没有这条候选词,也就是键盘完全不出来了,如果输入文字,候选词才会重新出来。总结来说就是这条候选词是可以关闭的,关闭之后只有当下次输入才会重新出现。(聚焦和失焦都只调用keyboardWillChangeFrameNotification和keyboardWillHideNotification通知,而且frame始终不变,都是在屏幕下面) + 7. iOS8 hide 之后高度变成0了,keyboardWillHideNotification还是正常的,所以建议不要使用键盘高度来做动画,而是用键盘的y值;在show和hide的时候endFrame会出现一些奇怪的中间值,但最终值是对的;两个输入框切换聚焦,iOS8不会触发任何键盘通知;iOS8的浮动切换正常; + 8. iOS8在 固定->浮动 的过程中,后面的keyboardWillChangeFrameNotification和keyboardWillHideNotification里面的endFrame是正确的,而iOS10和iOS9是错的,iOS9的y值是键盘的MaxY,而iOS10的y值是隐藏状态下的y,也就是屏幕高度。所以iOS9和iOS10需要在keyboardDidChangeFrameNotification里面重新刷新一下。 + */ @implementation QMUIKeyboardManager -// 1、系统键盘app启动第一次使用键盘的时候,会调用两轮键盘通知事件,之后就只会调用一次。而搜狗等第三方输入法的键盘,目前发现每次都会调用三次键盘通知事件。总之,键盘的通知事件是不确定的。 - -// 2、搜狗键盘可以修改键盘的高度,在修改键盘高度之后,会调用键盘的keyboardWillChangeFrameNotification和keyboardWillShowNotification通知。 - -// 3、如果从一个聚焦的输入框直接聚焦到另一个输入框,会调用前一个输入框的keyboardWillChangeFrameNotification,在调用后一个输入框的keyboardWillChangeFrameNotification,最后调用后一个输入框的keyboardWillShowNotification(如果此时是浮动键盘,那么后一个输入框的keyboardWillShowNotification不会被调用;)。 - -// 4、iPad可以变成浮动键盘,固定->浮动:会调用keyboardWillChangeFrameNotification和keyboardWillHideNotification;浮动->固定:会调用keyboardWillChangeFrameNotification和keyboardWillShowNotification;浮动键盘在移动的时候只会调用keyboardWillChangeFrameNotification通知,并且endFrame为zero,fromFrame不为zero,而是移动前键盘的frame。浮动键盘在聚焦和失焦的时候只会调用keyboardWillChangeFrameNotification,不会调用show和hide的notification。 - -// 5、iPad可以拆分为左右的小键盘,小键盘的通知具体基本跟浮动键盘一样。 - -// 6、iPad可以外接键盘,外接键盘之后屏幕上就没有虚拟键盘了,但是当我们输入文字的时候,发现底部还是有一条灰色的候选词,条东西也是键盘,它也会触发跟虚拟键盘一样的通知事件。如果点击这条候选词右边的向下箭头,则可以完全隐藏虚拟键盘,这个时候如果失焦再聚焦发现还是没有这条候选词,也就是键盘完全不出来了,如果输入文字,候选词才会重新出来。总结来说就是这条候选词是可以关闭的,关闭之后只有当下次输入才会重新出现。(聚焦和失焦都只调用keyboardWillChangeFrameNotification和keyboardWillHideNotification通知,而且frame始终不变,都是在屏幕下面) - -// 7、iOS8 hide 之后高度变成0了,keyboardWillHideNotification还是正常的,所以建议不要使用键盘高度来做动画,而是用键盘的y值;在show和hide的时候endFrame会出现一些奇怪的中间值,最终值是对的;两个输入框切换聚焦,iOS8不会触发任何键盘通知;iOS8的浮动切换正常; - -// 8、iOS8在 固定->浮动 的过程中,后面的keyboardWillChangeFrameNotification和keyboardWillHideNotification里面的endFrame是正确的,而iOS10和iOS9是错的,iOS9的y值是键盘的MaxY,而iOS10的y值是隐藏状态下的y,也就是屏幕高度。所以iOS9和iOS10需要在keyboardDidChangeFrameNotification里面重新刷新一下。 ++ (void)initialize { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + if (!kKeyboardManagerInstance) { + kKeyboardManagerInstance = [[QMUIKeyboardManager alloc] initWithDelegate:nil]; + } + }); +} - (instancetype)init { NSAssert(NO, @"请使用initWithDelegate:初始化"); @@ -382,6 +332,7 @@ - (void)keyboardWillShowNotification:(NSNotification *)notification { } QMUIKeyboardUserInfo *userInfo = [self newUserInfoWithNotification:notification]; + self.lastUserInfo = userInfo; userInfo.targetResponder = self.currentResponder ?: nil; if (self.delegateEnabled && [self.delegate respondsToSelector:@selector(keyboardWillShowWithUserInfo:)]) { @@ -401,6 +352,7 @@ - (void)keyboardDidShowNotification:(NSNotification *)notification { } QMUIKeyboardUserInfo *userInfo = [self newUserInfoWithNotification:notification]; + self.lastUserInfo = userInfo; userInfo.targetResponder = self.currentResponder ?: nil; id firstResponder = [[UIApplication sharedApplication].keyWindow qmui_findFirstResponder]; @@ -429,6 +381,7 @@ - (void)keyboardWillHideNotification:(NSNotification *)notification { } QMUIKeyboardUserInfo *userInfo = [self newUserInfoWithNotification:notification]; + self.lastUserInfo = userInfo; userInfo.targetResponder = self.currentResponder ?: nil; if (self.delegateEnabled && [self.delegate respondsToSelector:@selector(keyboardWillHideWithUserInfo:)]) { @@ -448,6 +401,7 @@ - (void)keyboardDidHideNotification:(NSNotification *)notification { } QMUIKeyboardUserInfo *userInfo = [self newUserInfoWithNotification:notification]; + self.lastUserInfo = userInfo; userInfo.targetResponder = self.currentResponder ?: nil; if ([self shouldReceiveHideNotification]) { @@ -473,6 +427,7 @@ - (void)keyboardWillChangeFrameNotification:(NSNotification *)notification { } QMUIKeyboardUserInfo *userInfo = [self newUserInfoWithNotification:notification]; + self.lastUserInfo = userInfo; if ([self shouldReceiveShowNotification]) { userInfo.targetResponder = self.currentResponder ?: nil; @@ -499,6 +454,7 @@ - (void)keyboardDidChangeFrameNotification:(NSNotification *)notification { } QMUIKeyboardUserInfo *userInfo = [self newUserInfoWithNotification:notification]; + self.lastUserInfo = userInfo; if ([self shouldReceiveShowNotification]) { userInfo.targetResponder = self.currentResponder ?: nil; @@ -543,92 +499,6 @@ - (BOOL)shouldReceiveHideNotification { } } -#pragma mark - iPad浮动键盘 - -- (void)addFrameObserverIfNeeded { - if (![self.class keyboardView]) { - return; - } - __weak __typeof(self)weakSelf = self; - QMUIKeyboardViewFrameObserver *observer = [QMUIKeyboardViewFrameObserver observerForView:[self.class keyboardView]]; - if (!observer) { - observer = [[QMUIKeyboardViewFrameObserver alloc] init]; - observer.keyboardViewChangeFrameBlock = ^(UIView *keyboardView) { - [weakSelf keyboardDidChangedFrame:keyboardView]; - }; - [observer addToKeyboardView:[self.class keyboardView]]; - // 手动调用第一次 - [self keyboardDidChangedFrame:[self.class keyboardView]]; - } -} - -- (void)keyboardDidChangedFrame:(UIView *)keyboardView { - - if (keyboardView != [self.class keyboardView]) { - return; - } - - // 也需要判断targetResponder - if (![self shouldReceiveShowNotification] && ![self shouldReceiveHideNotification]) { - return; - } - - if (self.delegateEnabled && [self.delegate respondsToSelector:@selector(keyboardWillChangeFrameWithUserInfo:)]) { - - UIWindow *keyboardWindow = keyboardView.window; - - if (self.keyboardMoveBeginRect.size.width == 0 && self.keyboardMoveBeginRect.size.height == 0) { - // 第一次需要初始化 - self.keyboardMoveBeginRect = CGRectMake(0, keyboardWindow.bounds.size.height, keyboardWindow.bounds.size.width, 0); - } - - CGRect endFrame = CGRectZero; - if (keyboardWindow) { - endFrame = [keyboardWindow convertRect:keyboardView.frame toWindow:nil]; - } else { - endFrame = keyboardView.frame; - } - - // 自己构造一个QMUIKeyboardUserInfo,一些属性使用之前最后一个keyboardUserInfo的值 - QMUIKeyboardUserInfo *keyboardMoveUserInfo = [[QMUIKeyboardUserInfo alloc] init]; - keyboardMoveUserInfo.keyboardManager = self; - keyboardMoveUserInfo.targetResponder = self.keyboardMoveUserInfo ? self.keyboardMoveUserInfo.targetResponder : nil; - keyboardMoveUserInfo.animationDuration = self.keyboardMoveUserInfo ? self.keyboardMoveUserInfo.animationDuration : 0.25; - keyboardMoveUserInfo.animationCurve = self.keyboardMoveUserInfo ? self.keyboardMoveUserInfo.animationCurve : 7; - keyboardMoveUserInfo.animationOptions = self.keyboardMoveUserInfo ? self.keyboardMoveUserInfo.animationOptions : keyboardMoveUserInfo.animationCurve<<16; - keyboardMoveUserInfo.beginFrame = self.keyboardMoveBeginRect; - keyboardMoveUserInfo.endFrame = endFrame; - - if (self.debug) { - QMUILog(NSStringFromClass(self.class), @"keyboardDidMoveNotification - %@", self); - } - - [self.delegate keyboardWillChangeFrameWithUserInfo:keyboardMoveUserInfo]; - - self.keyboardMoveBeginRect = endFrame; - - if (self.currentResponder) { - UIWindow *mainWindow = [UIApplication sharedApplication].keyWindow ?: [[UIApplication sharedApplication] windows].firstObject; - if (mainWindow) { - CGRect keyboardRect = keyboardMoveUserInfo.endFrame; - CGFloat distanceFromBottom = [QMUIKeyboardManager distanceFromMinYToBottomInView:mainWindow keyboardRect:keyboardRect]; - if (distanceFromBottom < keyboardRect.size.height) { - if (!self.currentResponder.keyboardManager_isFirstResponder) { - // willHide - self.currentResponder = nil; - } - } else if (distanceFromBottom > keyboardRect.size.height && !self.currentResponder.isFirstResponder) { - if (!self.currentResponder.keyboardManager_isFirstResponder) { - // 浮动 - self.currentResponder = nil; - } - } - } - } - - } -} - #pragma mark - 工具方法 + (void)animateWithAnimated:(BOOL)animated keyboardUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion { @@ -654,7 +524,7 @@ + (void)animateWithAnimated:(BOOL)animated keyboardUserInfo:(QMUIKeyboardUserInf + (void)handleKeyboardNotificationWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo showBlock:(void (^)(QMUIKeyboardUserInfo *keyboardUserInfo))showBlock hideBlock:(void (^)(QMUIKeyboardUserInfo *keyboardUserInfo))hideBlock { // 专门处理 iPad Pro 在键盘完全不显示的情况(不会调用willShow,所以通过是否focus来判断) - if ([QMUIKeyboardManager visiableKeyboardHeight] <= 0 && !keyboardUserInfo.isTargetResponderFocused) { + if ([QMUIKeyboardManager visibleKeyboardHeight] <= 0 && !keyboardUserInfo.isTargetResponderFocused) { if (hideBlock) { hideBlock(keyboardUserInfo); } @@ -797,10 +667,10 @@ + (BOOL)isKeyboardVisible { return NO; } CGRect rect = CGRectIntersection(keyboardWindow.bounds, keyboardView.frame); - if (CGRectIsNull(rect) || CGRectIsInfinite(rect)) { - return NO; + if (CGRectIsValidated(rect) && !CGRectIsEmpty(rect)) { + return YES; } - return rect.size.width > 0 && rect.size.height > 0; + return NO; } + (CGRect)currentKeyboardFrame { @@ -816,17 +686,17 @@ + (CGRect)currentKeyboardFrame { } } -+ (CGFloat)visiableKeyboardHeight { ++ (CGFloat)visibleKeyboardHeight { UIView *keyboardView = [self keyboardView]; UIWindow *keyboardWindow = keyboardView.window; if (!keyboardView || !keyboardWindow) { return 0; } else { - CGRect visiableRect = CGRectIntersection(keyboardWindow.bounds, keyboardView.frame); - if (CGRectIsNull(visiableRect)) { - return 0; + CGRect visibleRect = CGRectIntersection(keyboardWindow.bounds, keyboardView.frame); + if (CGRectIsValidated(visibleRect)) { + return CGRectGetHeight(visibleRect); } - return visiableRect.size.height; + return 0; } } @@ -906,14 +776,6 @@ - (void)setQmui_keyboardDidChangeFrameNotificationBlock:(void (^)(QMUIKeyboardUs return objc_getAssociatedObject(self, _cmd); } -//- (void)setQmui_keyboardManager:(QMUIKeyboardManager *)keyboardManager { -// objc_setAssociatedObject(self, @selector(qmui_keyboardManager), keyboardManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -//} -// -//- (QMUIKeyboardManager *)qmui_keyboardManager { -// return objc_getAssociatedObject(self, _cmd); -//} - - (void)initKeyboardManagerIfNeeded { if (!self.qmui_keyboardManager) { self.qmui_keyboardManager = [[QMUIKeyboardManager alloc] initWithDelegate:self]; @@ -1035,14 +897,6 @@ - (void)setQmui_keyboardDidChangeFrameNotificationBlock:(void (^)(QMUIKeyboardUs return objc_getAssociatedObject(self, _cmd); } -//- (void)setQmui_keyboardManager:(QMUIKeyboardManager *)keyboardManager { -// objc_setAssociatedObject(self, @selector(qmui_keyboardManager), keyboardManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC); -//} -// -//- (QMUIKeyboardManager *)qmui_keyboardManager { -// return objc_getAssociatedObject(self, _cmd); -//} - - (void)initKeyboardManagerIfNeeded { if (!self.qmui_keyboardManager) { self.qmui_keyboardManager = [[QMUIKeyboardManager alloc] initWithDelegate:self]; diff --git a/QMUIKit/QMUIComponents/QMUILogManagerViewController.m b/QMUIKit/QMUIComponents/QMUILogManagerViewController.m index 1b1dda71..b01037d6 100644 --- a/QMUIKit/QMUIComponents/QMUILogManagerViewController.m +++ b/QMUIKit/QMUIComponents/QMUILogManagerViewController.m @@ -153,24 +153,24 @@ - (void)handleMenuItemEvent { menuView.maximumWidth = 124; menuView.safetyMarginsOfSuperview = UIEdgeInsetsSetRight(menuView.safetyMarginsOfSuperview, 6); menuView.items = @[ - [QMUIPopupMenuItem itemWithImage:nil title:@"开启全部" handler:^{ + [QMUIPopupMenuItem itemWithImage:nil title:@"开启全部" handler:^(QMUIPopupMenuView *aMenuView, QMUIPopupMenuItem *aItem) { for (NSString *logName in self.allNames) { [[QMUILogger sharedInstance].logNameManager setEnabled:YES forLogName:logName]; } [self reloadData]; - [menuView hideWithAnimated:YES]; + [aMenuView hideWithAnimated:YES]; }], - [QMUIPopupMenuItem itemWithImage:nil title:@"禁用全部" handler:^{ + [QMUIPopupMenuItem itemWithImage:nil title:@"禁用全部" handler:^(QMUIPopupMenuView *aMenuView, QMUIPopupMenuItem *aItem) { for (NSString *logName in self.allNames) { [[QMUILogger sharedInstance].logNameManager setEnabled:NO forLogName:logName]; } [self reloadData]; - [menuView hideWithAnimated:YES]; + [aMenuView hideWithAnimated:YES]; }], - [QMUIPopupMenuItem itemWithImage:nil title:@"清空全部" handler:^{ + [QMUIPopupMenuItem itemWithImage:nil title:@"清空全部" handler:^(QMUIPopupMenuView *aMenuView, QMUIPopupMenuItem *aItem) { [[QMUILogger sharedInstance].logNameManager removeAllNames]; [self reloadData]; - [menuView hideWithAnimated:YES]; + [aMenuView hideWithAnimated:YES]; }]]; [menuView layoutWithTargetView:self.navigationItem.rightBarButtonItem.qmui_view]; [menuView showWithAnimated:YES]; diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h index b2c725c0..14168c4d 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h @@ -24,10 +24,11 @@ typedef NS_ENUM(NSUInteger, QMUIModalPresentationAnimationStyle) { /** * 当浮层以 UIViewController 的形式展示(而非 UIView),并且使用 modalController 提供的默认布局时,则可通过这个方法告诉 modalController 当前浮层期望的大小 * @param controller 当前的modalController - * @param limitSize 浮层最大的宽高,由当前 modalController 的大小及 `contentViewMargins`、`maximumContentViewWidth` 决定 + * @param keyboardHeight 当前的键盘高度,如果键盘降下,则为0 + * @param limitSize 浮层最大的宽高,由当前 modalController 的大小及 `contentViewMargins`、`maximumContentViewWidth` 和键盘高度决定 * @return 返回浮层在 `limitSize` 限定内的大小,如果业务自身不需要限制宽度/高度,则为 width/height 返回 `CGFLOAT_MAX` 即可 */ -- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller limitSize:(CGSize)limitSize; +- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller keyboardHeight:(CGFloat)keyboardHeight limitSize:(CGSize)limitSize; @end @@ -82,8 +83,8 @@ typedef NS_ENUM(NSUInteger, QMUIModalPresentationAnimationStyle) { * @endcode * * 默认的布局会将浮层居中显示,浮层的大小可通过接口控制: - * 1. 如果是用 `contentViewController`,则可通过 `preferredContentSizeInModalPresentationViewController:limitSize:` 来设置 - * 2. 如果使用 `contentView`,或者使用 `contentViewController` 但没实现 `preferredContentSizeInModalPresentationViewController:limitSize:`,则调用`contentView`的`sizeThatFits:`方法获取大小。 + * 1. 如果是用 `contentViewController`,则可通过 `preferredContentSizeInModalPresentationViewController:keyboardHeight:limitSize:` 来设置 + * 2. 如果使用 `contentView`,或者使用 `contentViewController` 但没实现 `preferredContentSizeInModalPresentationViewController:keyboardHeight:limitSize:`,则调用`contentView`的`sizeThatFits:`方法获取大小。 * 3. 浮层大小会受 `maximumContentViewWidth` 属性的限制,以及 `contentViewMargins` 属性的影响。 * * 通过`layoutBlock`、`showingAnimation`、`hidingAnimation`可设置自定义的布局、打开及隐藏的动画,并允许你适配键盘升起时的场景。 diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m index 147accf2..db7b9eb1 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m @@ -303,6 +303,7 @@ - (void)viewWillDisappear:(BOOL)animated { - (void)updateLayout { if ([self isViewLoaded]) { [self.view setNeedsLayout]; + [self.view layoutIfNeeded]; } } @@ -511,8 +512,8 @@ - (CGRect)contentViewFrameForShowing { CGSize contentViewContainerSize = CGSizeMake(CGRectGetWidth(self.view.bounds) - UIEdgeInsetsGetHorizontalValue(self.contentViewMargins), CGRectGetHeight(self.view.bounds) - self.keyboardHeight - UIEdgeInsetsGetVerticalValue(self.contentViewMargins)); CGSize contentViewLimitSize = CGSizeMake(fmin(self.maximumContentViewWidth, contentViewContainerSize.width), contentViewContainerSize.height); CGSize contentViewSize = CGSizeZero; - if ([self.contentViewController respondsToSelector:@selector(preferredContentSizeInModalPresentationViewController:limitSize:)]) { - contentViewSize = [self.contentViewController preferredContentSizeInModalPresentationViewController:self limitSize:contentViewLimitSize]; + if ([self.contentViewController respondsToSelector:@selector(preferredContentSizeInModalPresentationViewController:keyboardHeight:limitSize:)]) { + contentViewSize = [self.contentViewController preferredContentSizeInModalPresentationViewController:self keyboardHeight:self.keyboardHeight limitSize:contentViewLimitSize]; } else { contentViewSize = [self.contentView sizeThatFits:contentViewLimitSize]; } @@ -543,18 +544,12 @@ - (BOOL)isShowingPresentedViewController { #pragma mark - -- (void)keyboardWillShowWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo { +- (void)keyboardWillChangeFrameWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo { CGRect keyboardRect = [QMUIKeyboardManager convertKeyboardRect:[keyboardUserInfo endFrame] toView:self.view]; - CGFloat keyboardHeight = keyboardRect.size.height; - if (keyboardHeight <= 0) return; - + CGFloat keyboardHeight = CGRectIntersection(self.view.bounds, keyboardRect).size.height; self.keyboardHeight = keyboardHeight; - [self.view setNeedsLayout]; -} - -- (void)keyboardWillHideWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo { - self.keyboardHeight = 0; - [self.view setNeedsLayout]; + + [self updateLayout]; } #pragma mark - 屏幕旋转 diff --git a/QMUIKit/QMUIComponents/QMUIMoreOperationController.m b/QMUIKit/QMUIComponents/QMUIMoreOperationController.m index 442995dd..cb3dfc6f 100644 --- a/QMUIKit/QMUIComponents/QMUIMoreOperationController.m +++ b/QMUIKit/QMUIComponents/QMUIMoreOperationController.m @@ -612,7 +612,7 @@ - (void)updateCornerRadius { #pragma mark - -- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller limitSize:(CGSize)limitSize { +- (CGSize)preferredContentSizeInModalPresentationViewController:(QMUIModalPresentationViewController *)controller keyboardHeight:(CGFloat)keyboardHeight limitSize:(CGSize)limitSize { __block CGFloat contentHeight = (self.cancelButton.hidden ? 0 : self.cancelButtonHeight + self.cancelButtonMarginTop); [self.mutableScrollViews enumerateObjectsUsingBlock:^(UIScrollView * _Nonnull scrollView, NSUInteger idx, BOOL * _Nonnull stop) { NSArray *itemSection = self.mutableItems[idx]; diff --git a/QMUIKit/QMUIComponents/QMUIPopupMenuView.h b/QMUIKit/QMUIComponents/QMUIPopupMenuView.h index 7442dc81..da22943a 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupMenuView.h +++ b/QMUIKit/QMUIComponents/QMUIPopupMenuView.h @@ -50,7 +50,8 @@ @property(nonatomic, strong) UIImage *image; @property(nonatomic, copy) NSString *title; @property(nonatomic, strong, readonly) QMUIButton *button; -@property(nonatomic, copy) void (^handler)(void); +@property(nonatomic, copy) void (^handler)(QMUIPopupMenuView *aMenuView, QMUIPopupMenuItem *aItem); +@property(nonatomic, weak) QMUIPopupMenuView *menuView; -+ (instancetype)itemWithImage:(UIImage *)image title:(NSString *)title handler:(void (^)(void))handler; ++ (instancetype)itemWithImage:(UIImage *)image title:(NSString *)title handler:(void (^)(QMUIPopupMenuView *aMenuView, QMUIPopupMenuItem *aItem))handler; @end diff --git a/QMUIKit/QMUIComponents/QMUIPopupMenuView.m b/QMUIKit/QMUIComponents/QMUIPopupMenuView.m index 284bfe54..534f4709 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupMenuView.m +++ b/QMUIKit/QMUIComponents/QMUIPopupMenuView.m @@ -11,6 +11,7 @@ #import "UIView+QMUI.h" #import "CALayer+QMUI.h" #import "UIButton+QMUI.h" +#import "NSArray+QMUI.h" #import "QMUICore.h" @interface QMUIPopupMenuItem () @@ -32,11 +33,17 @@ - (void)updateAppearanceForPopupMenuView; @implementation QMUIPopupMenuView - (void)setItems:(NSArray *)items { + [_items enumerateObjectsUsingBlock:^(QMUIPopupMenuItem * _Nonnull item, NSUInteger idx, BOOL * _Nonnull stop) { + item.menuView = nil; + }]; _items = items; self.itemSections = @[_items]; } - (void)setItemSections:(NSArray *> *)itemSections { + [_itemSections qmui_enumerateNestedArrayWithBlock:^(QMUIPopupMenuItem *item, BOOL *stop) { + item.menuView = nil; + }]; _itemSections = itemSections; [self configureItems]; } @@ -60,6 +67,7 @@ - (void)configureItems { item.button.imageEdgeInsets = UIEdgeInsetsMake(0, -self.imageMarginRight, 0, self.imageMarginRight); item.button.contentEdgeInsets = UIEdgeInsetsMake(0, self.padding.left - item.button.imageEdgeInsets.left, 0, self.padding.right); [self.scrollView addSubview:item.button]; + item.menuView = self; // 配置分隔线,注意每一个 section 里的最后一行是不显示分隔线的 BOOL shouldShowSeparatorAtRow = [self shouldShowSeparatorAtRow:row rowCount:rowCount inSection:section sectionCount:sectionCount]; @@ -175,7 +183,7 @@ - (void)updateAppearanceForPopupMenuView { @implementation QMUIPopupMenuItem -+ (instancetype)itemWithImage:(UIImage *)image title:(NSString *)title handler:(void (^)(void))handler { ++ (instancetype)itemWithImage:(UIImage *)image title:(NSString *)title handler:(void (^)(QMUIPopupMenuView *, QMUIPopupMenuItem *))handler { QMUIPopupMenuItem *item = [[QMUIPopupMenuItem alloc] init]; item.image = image; item.title = title; @@ -201,7 +209,7 @@ - (void)setImage:(UIImage *)image { - (void)handleButtonEvent:(id)sender { if (self.handler) { - self.handler(); + self.handler(self.menuView, self); } } diff --git a/QMUIKit/QMUICore/QMUICommonDefines.h b/QMUIKit/QMUICore/QMUICommonDefines.h index 50bcb78a..eee686a3 100644 --- a/QMUIKit/QMUICore/QMUICommonDefines.h +++ b/QMUIKit/QMUICore/QMUICommonDefines.h @@ -221,12 +221,6 @@ ExchangeImplementationsInTwoClasses(Class _fromClass, SEL _originSelector, Class return NO; } - Class superclass = class_getSuperclass(_fromClass); - BOOL tryToExchangeSuperclassMethod = [superclass instancesRespondToSelector:_originSelector] && (class_getInstanceMethod(superclass, _originSelector) == class_getInstanceMethod(_fromClass, _originSelector)); - if (tryToExchangeSuperclassMethod) { - NSLog(@"注意,%@ 准备替换方法 %@, 但这个方法来自于父类 %@", NSStringFromClass(_fromClass), NSStringFromSelector(_originSelector), NSStringFromClass(superclass)); - } - BOOL isAddedMethod = class_addMethod(_fromClass, _originSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod)); if (isAddedMethod) { // 如果 class_addMethod 成功了,说明之前 fromClass 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续 toClass 的这个方法被调用时可能会 crash diff --git a/QMUIKit/QMUICore/QMUIHelper.m b/QMUIKit/QMUICore/QMUIHelper.m index 89028ce4..3cc84efa 100644 --- a/QMUIKit/QMUICore/QMUIHelper.m +++ b/QMUIKit/QMUICore/QMUIHelper.m @@ -8,6 +8,7 @@ #import "QMUIHelper.h" #import "QMUICore.h" +#import "NSNumber+QMUI.h" #import #import @@ -102,7 +103,7 @@ + (NSNumber *)preferredContentSizeLevel { + (CGFloat)heightForDynamicTypeCell:(NSArray *)heights { NSNumber *index = [QMUIHelper preferredContentSizeLevel]; - return [((NSNumber *)[heights objectAtIndex:[index intValue]]) floatValue]; + return [((NSNumber *)[heights objectAtIndex:[index intValue]]) qmui_CGFloatValue]; } @end @@ -137,7 +138,7 @@ - (void)setLastKeyboardHeight:(CGFloat)argv { } - (CGFloat)lastKeyboardHeight { - return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_LastKeyboardHeight)) floatValue]; + return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_LastKeyboardHeight)) qmui_CGFloatValue]; } + (CGFloat)lastKeyboardHeightInApplicationWindowWhenVisible { diff --git a/QMUIKit/UIKitExtensions/UILabel+QMUI.m b/QMUIKit/UIKitExtensions/UILabel+QMUI.m index 26cdfc09..7958fa01 100644 --- a/QMUIKit/UIKitExtensions/UILabel+QMUI.m +++ b/QMUIKit/UIKitExtensions/UILabel+QMUI.m @@ -10,6 +10,7 @@ #import "QMUICore.h" #import "NSParagraphStyle+QMUI.h" #import "NSObject+QMUI.h" +#import "NSNumber+QMUI.h" @implementation UILabel (QMUI) @@ -148,7 +149,7 @@ - (void)setQmui_lineHeight:(CGFloat)qmui_lineHeight { } - (CGFloat)qmui_lineHeight { - return [objc_getAssociatedObject(self, &kAssociatedObjectKey_lineHeight) floatValue]; + return [(NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_lineHeight) qmui_CGFloatValue]; } - (instancetype)qmui_initWithFont:(UIFont *)font textColor:(UIColor *)textColor { diff --git a/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m b/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m index 20b325c7..8791f6b2 100644 --- a/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m @@ -23,10 +23,6 @@ - (NSString *)qmui_description { } - (BOOL)qmui_alreadyAtTop { - if (!self.qmui_canScroll) { - return YES; - } - if (self.contentOffset.y == -self.qmui_contentInset.top) { return YES; } diff --git a/QMUIKit/UIKitExtensions/UITextView+QMUI.m b/QMUIKit/UIKitExtensions/UITextView+QMUI.m index 2af1cc5e..383ab309 100644 --- a/QMUIKit/UIKitExtensions/UITextView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITextView+QMUI.m @@ -37,7 +37,7 @@ - (void)qmui_scrollCaretVisibleAnimated:(BOOL)animated { CGRect caretRect = [self caretRectForPosition:self.selectedTextRange.end]; // scrollEnabled 为 NO 时可能产生不合法的 rect 值 https://github.com/QMUI/QMUI_iOS/issues/205 - if (isinf(CGRectGetMinX(caretRect)) || isinf(CGRectGetMinY(caretRect))) { + if (!CGRectIsValidated(caretRect)) { return; } diff --git a/QMUIKit/UIKitExtensions/UIView+QMUI.m b/QMUIKit/UIKitExtensions/UIView+QMUI.m index 407a0c4a..e472190d 100644 --- a/QMUIKit/UIKitExtensions/UIView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIView+QMUI.m @@ -12,6 +12,7 @@ #import "UIColor+QMUI.h" #import "NSObject+QMUI.h" #import "UIImage+QMUI.h" +#import "NSNumber+QMUI.h" #import @interface UIView () @@ -414,7 +415,7 @@ - (void)setQmui_borderWidth:(CGFloat)qmui_borderWidth { } - (CGFloat)qmui_borderWidth { - return (CGFloat)[objc_getAssociatedObject(self, &kAssociatedObjectKey_borderWidth) floatValue]; + return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_borderWidth)) qmui_CGFloatValue]; } static char kAssociatedObjectKey_borderColor; @@ -434,7 +435,7 @@ - (void)setQmui_dashPhase:(CGFloat)qmui_dashPhase { } - (CGFloat)qmui_dashPhase { - return (CGFloat)[objc_getAssociatedObject(self, &kAssociatedObjectKey_dashPhase) floatValue]; + return [(NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_dashPhase) qmui_CGFloatValue]; } static char kAssociatedObjectKey_dashPattern; diff --git a/qmui.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/qmui.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 919434a6..00000000 --- a/qmui.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/qmui.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/qmui.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d98100..00000000 --- a/qmui.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - -