diff --git a/QMUIKit.podspec b/QMUIKit.podspec index 4fce1a54..033870a5 100644 --- a/QMUIKit.podspec +++ b/QMUIKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "QMUIKit" - s.version = "4.5.0" + s.version = "4.5.1" s.summary = "致力于提高项目 UI 开发效率的解决方案" s.description = <<-DESC QMUI iOS 是一个致力于提高项目 UI 开发效率的解决方案,其设计目的是用于辅助快速搭建一个具备基本设计还原效果的 iOS 项目,同时利用自身提供的丰富控件及兼容处理, 让开发者能专注于业务需求而无需耗费精力在基础代码的设计上。不管是新项目的创建,或是已有项目的维护,均可使开发效率和项目质量得到大幅度提升。 diff --git a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m index 6aed3a52..9ea83284 100644 --- a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m +++ b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m @@ -54,16 +54,16 @@ + (void)load { } }); - OverrideImplementation([UINavigationBar class], @selector(setBackgroundImage:forBarPosition:barMetrics:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UINavigationBar *selfObject, UIImage *image, UIBarPosition barPosition, UIBarMetrics barMetrics) { + OverrideImplementation([UINavigationBar class], @selector(setBackgroundImage:forBarMetrics:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UINavigationBar *selfObject, UIImage *image, UIBarMetrics barMetrics) { // call super - void (*originSelectorIMP)(id, SEL, UIImage *, UIBarPosition, UIBarMetrics); - originSelectorIMP = (void (*)(id, SEL, UIImage *, UIBarPosition, UIBarMetrics))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, image, barPosition, barMetrics); + void (*originSelectorIMP)(id, SEL, UIImage *, UIBarMetrics); + originSelectorIMP = (void (*)(id, SEL, UIImage *, UIBarMetrics))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, image, barMetrics); if (selfObject.qmuinb_copyStylesToBar) { - [selfObject.qmuinb_copyStylesToBar setBackgroundImage:image forBarPosition:barPosition barMetrics:barMetrics]; + [selfObject.qmuinb_copyStylesToBar setBackgroundImage:image forBarMetrics:barMetrics]; } }; }); @@ -165,6 +165,7 @@ + (void)load { // iOS 14 开启 customNavigationBarTransitionKey 的情况下转场效果错误 // https://github.com/Tencent/QMUI_iOS/issues/1081 if (@available(iOS 14.0, *)) { + // - [UINavigationBar _accessibility_navigationController] OverrideImplementation([_QMUITransitionNavigationBar class], NSSelectorFromString([NSString stringWithFormat:@"_%@_%@", @"accessibility", @"navigationController"]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^UINavigationController *(_QMUITransitionNavigationBar *selfObject) { if (selfObject.originalNavigationBar) { @@ -184,7 +185,7 @@ + (void)load { #ifdef IOS15_SDK_ALLOWED if (@available(iOS 15.0, *)) { - // -[UINavigationBar _didMoveFromWindow:toWindow:] + // - [UINavigationBar _didMoveFromWindow:toWindow:] OverrideImplementation([_QMUITransitionNavigationBar class], NSSelectorFromString(@"_didMoveFromWindow:toWindow:"), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^(_QMUITransitionNavigationBar *selfObject, UIWindow *firstArgv, UIWindow *secondArgv) { diff --git a/QMUIKit/QMUIComponents/QMUIAlertController.m b/QMUIKit/QMUIComponents/QMUIAlertController.m index 053466c7..c75b865e 100644 --- a/QMUIKit/QMUIComponents/QMUIAlertController.m +++ b/QMUIKit/QMUIComponents/QMUIAlertController.m @@ -536,6 +536,7 @@ - (void)viewDidLayoutSubviews { BOOL hasMessage = (self.messageLabel.text.length > 0 && !self.messageLabel.hidden); BOOL hasTextField = self.alertTextFields.count > 0; BOOL hasCustomView = !!_customView; + BOOL shouldShowSeparatorAtTopOfButtonAtFirstLine = hasTitle || hasMessage || hasCustomView; CGFloat contentOriginY = 0; self.maskView.frame = self.view.bounds; @@ -606,16 +607,21 @@ - (void)viewDidLayoutSubviews { // 对齐系统,先 add 的在右边,后 add 的在左边 QMUIAlertAction *leftAction = newOrderActions[1]; leftAction.button.frame = CGRectMake(0, contentOriginY, CGRectGetWidth(self.buttonScrollView.bounds) / 2, self.alertButtonHeight); - leftAction.button.qmui_borderPosition = QMUIViewBorderPositionTop|QMUIViewBorderPositionRight; + leftAction.button.qmui_borderPosition = QMUIViewBorderPositionRight; QMUIAlertAction *rightAction = newOrderActions[0]; rightAction.button.frame = CGRectMake(CGRectGetMaxX(leftAction.button.frame), contentOriginY, CGRectGetWidth(self.buttonScrollView.bounds) / 2, self.alertButtonHeight); - rightAction.button.qmui_borderPosition = QMUIViewBorderPositionTop; + if (shouldShowSeparatorAtTopOfButtonAtFirstLine) { + leftAction.button.qmui_borderPosition |= QMUIViewBorderPositionTop; + rightAction.button.qmui_borderPosition = QMUIViewBorderPositionTop; + } contentOriginY = CGRectGetMaxY(leftAction.button.frame); } else { for (int i = 0; i < newOrderActions.count; i++) { QMUIAlertAction *action = newOrderActions[i]; action.button.frame = CGRectMake(0, contentOriginY, CGRectGetWidth(self.containerView.bounds), self.alertButtonHeight); - action.button.qmui_borderPosition = QMUIViewBorderPositionTop; + if (i > 0 || shouldShowSeparatorAtTopOfButtonAtFirstLine) { + action.button.qmui_borderPosition = QMUIViewBorderPositionTop; + } contentOriginY = CGRectGetMaxY(action.button.frame); } } @@ -717,17 +723,25 @@ - (void)viewDidLayoutSubviews { if (action.style == QMUIAlertActionStyleCancel && i == newOrderActions.count - 1) { continue; } else { + BOOL isFirstLine = floor(i / columnCount) == 0; + BOOL isLastColumn = fmod(i + 1, columnCount) == 0; + BOOL shouldShowSeparatorAtTop = !isFirstLine || shouldShowSeparatorAtTopOfButtonAtFirstLine; + BOOL shouldShowSeparatorAtRight = !isLastColumn;// 单列时全都不用显示右分隔线,多列时最后一列不用显示右分隔线 action.button.frame = CGRectMake(alertActionsLayoutX, alertActionsLayoutY, alertActionsWidth, self.sheetButtonHeight); - if (fmodf(i + 1, columnCount) == 0) { - action.button.qmui_borderPosition = QMUIViewBorderPositionTop; + if (isLastColumn) { alertActionsLayoutX = 0; alertActionsLayoutY = CGRectGetMaxY(action.button.frame); } else { - action.button.qmui_borderPosition = QMUIViewBorderPositionTop|QMUIViewBorderPositionRight; alertActionsLayoutX += alertActionsWidth; } - contentOriginY = MAX(contentOriginY, CGRectGetMaxY(action.button.frame)); + + if (shouldShowSeparatorAtTop) { + action.button.qmui_borderPosition |= QMUIViewBorderPositionTop; + } + if (shouldShowSeparatorAtRight) { + action.button.qmui_borderPosition |= QMUIViewBorderPositionRight; + } } } } diff --git a/QMUIKit/QMUIKit.h b/QMUIKit/QMUIKit.h index dd7fa8a5..b3768d52 100644 --- a/QMUIKit/QMUIKit.h +++ b/QMUIKit/QMUIKit.h @@ -13,7 +13,7 @@ #ifndef QMUIKit_h #define QMUIKit_h -static NSString * const QMUI_VERSION = @"4.5.0"; +static NSString * const QMUI_VERSION = @"4.5.1"; #if __has_include("CAAnimation+QMUI.h") #import "CAAnimation+QMUI.h" diff --git a/QMUIKit/UIKitExtensions/NSObject+QMUI.h b/QMUIKit/UIKitExtensions/NSObject+QMUI.h index ff43138b..22334fa1 100644 --- a/QMUIKit/UIKitExtensions/NSObject+QMUI.h +++ b/QMUIKit/UIKitExtensions/NSObject+QMUI.h @@ -301,7 +301,7 @@ NS_ASSUME_NONNULL_BEGIN /// 获取当前对象的所有 @property、方法,不包含父类的 @property(nonatomic, copy, readonly) NSString *qmui_shortMethodList; -/// 获取当前对象的所有 Ivar 变量 +/// 获取当前对象的所有 Ivar 变量,并在 Ivar 名字前面显示该 Ivar 的 offset,会同时显示十进制和十六进制,以“|”隔开。 @property(nonatomic, copy, readonly) NSString *qmui_ivarList; /// 获取当前 UIView 层级树信息(只对 UIView 有效) diff --git a/QMUIKit/UIKitExtensions/NSObject+QMUI.m b/QMUIKit/UIKitExtensions/NSObject+QMUI.m index 9d327741..93d3624b 100644 --- a/QMUIKit/UIKitExtensions/NSObject+QMUI.m +++ b/QMUIKit/UIKitExtensions/NSObject+QMUI.m @@ -130,7 +130,9 @@ - (void)qmui_enumrateIvarsUsingBlock:(void (^)(Ivar ivar, NSString *ivarDescript - (void)qmui_enumrateIvarsIncludingInherited:(BOOL)includingInherited usingBlock:(void (^)(Ivar ivar, NSString *ivarDescription))block { NSMutableArray *ivarDescriptions = [NSMutableArray new]; - NSString *ivarList = [self qmui_ivarList]; + BeginIgnorePerformSelectorLeaksWarning + NSString *ivarList = [self performSelector:NSSelectorFromString(@"_ivarDescription")]; + EndIgnorePerformSelectorLeaksWarning NSError *error; NSRegularExpression *reg = [NSRegularExpression regularExpressionWithPattern:[NSString stringWithFormat:@"in %@:(.*?)((?=in \\w+:)|$)", NSStringFromClass(self.class)] options:NSRegularExpressionDotMatchesLineSeparators error:&error]; if (!error) { @@ -456,7 +458,31 @@ - (NSString *)qmui_shortMethodList { } - (NSString *)qmui_ivarList { - return [self performSelector:NSSelectorFromString(@"_ivarDescription")]; + NSString *systemResult = [self performSelector:NSSelectorFromString(@"_ivarDescription")]; + NSRegularExpression *regx = [NSRegularExpression regularExpressionWithPattern:@"^(\\s+)(\\S+)" options:NSRegularExpressionCaseInsensitive error:nil]; + NSMutableArray *lines = [systemResult componentsSeparatedByString:@"\n"].mutableCopy; + [lines enumerateObjectsUsingBlock:^(NSString *line, NSUInteger idx, BOOL * _Nonnull stop) { + + // 过滤掉空行或者 struct 结尾的"}" + if (line.qmui_trim.length <= 2) return; + + // 有些 struct 类型的变量,会把 struct 的成员也缩进打出来,所以用这种方式过滤掉 + if ([line hasPrefix:@"\t\t"]) return; + + NSTextCheckingResult *regxResult = [regx firstMatchInString:line options:NSMatchingReportCompletion range:NSMakeRange(0, line.length)]; + if (regxResult.numberOfRanges < 3) return; + + NSRange indentRange = [regxResult rangeAtIndex:1]; + NSRange offsetRange = NSMakeRange(NSMaxRange(indentRange), 0); + NSRange ivarNameRange = [regxResult rangeAtIndex:2]; + NSString *ivarName = [line substringWithRange:ivarNameRange]; + Ivar ivar = class_getInstanceVariable(self.class, ivarName.UTF8String); + ptrdiff_t ivarOffset = ivar_getOffset(ivar); + NSString *lineWithOffset = [line stringByReplacingCharactersInRange:offsetRange withString:[NSString stringWithFormat:@"[%@|0x%@]", @(ivarOffset), [NSString stringWithFormat:@"%lx", (NSInteger)ivarOffset].uppercaseString]]; + [lines setObject:lineWithOffset atIndexedSubscript:idx]; + }]; + NSString *result = [lines componentsJoinedByString:@"\n"]; + return result; } - (NSString *)qmui_viewInfo { diff --git a/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m b/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m index 30daa480..d5e761ae 100644 --- a/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIScrollView+QMUI.m @@ -74,7 +74,7 @@ + (void)load { } - (BOOL)qmui_alreadyAtTop { - if (((NSInteger)self.contentOffset.y) == -((NSInteger)self.adjustedContentInset.top)) { + if (CGFloatEqualToFloat(self.contentOffset.y, -self.adjustedContentInset.top)) { return YES; } diff --git a/QMUIKit/UIKitExtensions/UIView+QMUI.m b/QMUIKit/UIKitExtensions/UIView+QMUI.m index 2480b42b..4d33514d 100644 --- a/QMUIKit/UIKitExtensions/UIView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIView+QMUI.m @@ -88,7 +88,10 @@ - (void)setQmui_outsideEdge:(UIEdgeInsets)qmui_outsideEdge { OverrideImplementation([UIView class], @selector(pointInside:withEvent:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^BOOL(UIControl *selfObject, CGPoint point, UIEvent *event) { - if (!UIEdgeInsetsEqualToEdgeInsets(selfObject.qmui_outsideEdge, UIEdgeInsetsZero)) { + if (!UIEdgeInsetsEqualToEdgeInsets(selfObject.qmui_outsideEdge, UIEdgeInsetsZero) + && selfObject.alpha > 0.01 + && !selfObject.hidden + && !CGRectIsEmpty(selfObject.frame)) { CGRect rect = UIEdgeInsetsInsetRect(selfObject.bounds, selfObject.qmui_outsideEdge); BOOL result = CGRectContainsPoint(rect, point); return result; diff --git a/qmui.xcodeproj/project.pbxproj b/qmui.xcodeproj/project.pbxproj index 743ffe78..2ddcd42f 100644 --- a/qmui.xcodeproj/project.pbxproj +++ b/qmui.xcodeproj/project.pbxproj @@ -1960,7 +1960,7 @@ "@loader_path/Frameworks", ); MACH_O_TYPE = mh_dylib; - MARKETING_VERSION = 4.5.0; + MARKETING_VERSION = 4.5.1; MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.qmui.QMUIKit; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2004,7 +2004,7 @@ "@loader_path/Frameworks", ); MACH_O_TYPE = mh_dylib; - MARKETING_VERSION = 4.5.0; + MARKETING_VERSION = 4.5.1; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.qmui.QMUIKit; PRODUCT_NAME = "$(TARGET_NAME)";