From b160a0fbf484d73703f487a0c2d6ffdc1cbce49b Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Thu, 11 Sep 2025 17:24:18 +0800 Subject: [PATCH 01/22] =?UTF-8?q?1=E3=80=81=E4=BF=AE=E5=A4=8D=E4=B8=80?= =?UTF-8?q?=E7=B3=BB=E5=88=97issue=202=E3=80=81ruanmei-fix=EF=BC=8C?= =?UTF-8?q?=E9=80=82=E9=85=8DLiquidGlass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UINavigationBar+Transition.m | 31 ++++- ...gationController+NavigationBarTransition.m | 2 + .../QMUIBadge/UIBarItem+QMUIBadge.m | 61 +++++++++ .../QMUIBadge/UIView+QMUIBadge.m | 2 +- .../QMUIButton/QMUINavigationButton.m | 70 +++++----- .../QMUIComponents/QMUIConsole/QMUIConsole.m | 32 +++-- QMUIKit/QMUIComponents/QMUIKeyboardManager.m | 23 ++-- .../QMUIModalPresentationViewController.h | 2 +- .../QMUIModalPresentationViewController.m | 13 +- .../QMUIComponents/QMUIPopupContainerView.m | 17 +-- QMUIKit/QMUIComponents/QMUISearchController.m | 9 +- QMUIKit/QMUIComponents/QMUITextView.m | 14 +- .../QMUITheme/QMUIThemeManager.m | 3 +- .../QMUITheme/QMUIThemePrivate.m | 13 ++ .../QMUITheme/UIColor+QMUITheme.m | 2 +- .../QMUITheme/UIView+QMUITheme.m | 9 ++ QMUIKit/QMUIComponents/QMUITips.h | 5 +- QMUIKit/QMUIComponents/QMUITips.m | 20 +-- .../QMUIComponents/QMUIWindowSizeMonitor.m | 3 +- QMUIKit/QMUICore/QMUICommonDefines.h | 5 + QMUIKit/QMUICore/QMUIConfiguration.m | 2 +- QMUIKit/QMUICore/QMUIHelper.h | 3 + QMUIKit/QMUICore/QMUIHelper.m | 30 ++++- QMUIKit/UIKitExtensions/UIApplication+QMUI.h | 6 + QMUIKit/UIKitExtensions/UIApplication+QMUI.m | 50 ++++++++ QMUIKit/UIKitExtensions/UIBarItem+QMUI.m | 24 +++- QMUIKit/UIKitExtensions/UIColor+QMUI.m | 2 +- QMUIKit/UIKitExtensions/UIInterface+QMUI.m | 3 +- .../UIKitExtensions/UIMenuController+QMUI.m | 7 +- .../UIKitExtensions/UINavigationBar+QMUI.m | 24 +++- QMUIKit/UIKitExtensions/UISearchBar+QMUI.m | 120 ++++++++++++------ QMUIKit/UIKitExtensions/UISlider+QMUI.m | 28 ++-- QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m | 11 +- QMUIKit/UIKitExtensions/UITableView+QMUI.m | 10 ++ .../UIKitExtensions/UITraitCollection+QMUI.m | 62 +++------ QMUIKit/UIKitExtensions/UIView+QMUI.m | 22 +++- .../UIKitExtensions/UIViewController+QMUI.m | 5 +- QMUIKit/UIKitExtensions/UIWindow+QMUI.h | 9 +- QMUIKit/UIKitExtensions/UIWindow+QMUI.m | 11 +- 39 files changed, 550 insertions(+), 215 deletions(-) diff --git a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m index 3e1c5ea7..08c444dc 100644 --- a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m +++ b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m @@ -18,7 +18,9 @@ #import "UINavigationBar+QMUI.h" #import "UINavigationBar+QMUIBarProtocol.h" #import "QMUIWeakObjectContainer.h" +#import "QMUIBarProtocolPrivate.h" #import "UIImage+QMUI.h" +#import "CALayer+QMUI.h" @implementation UINavigationBar (Transition) @@ -51,7 +53,7 @@ + (void)load { originSelectorIMP(selfObject, originCMD, appearance); if (selfObject.qmuinb_copyStylesToBar) { - selfObject.qmuinb_copyStylesToBar.standardAppearance = appearance; + selfObject.qmuinb_copyStylesToBar.scrollEdgeAppearance = appearance; } }; }); @@ -222,6 +224,21 @@ + (void)load { }); } + OverrideImplementation(NSClassFromString(@"_UIBarBackground"), @selector(didMoveToWindow), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIView *selfObject) { + + // call super + void (*originSelectorIMP)(id, SEL); + originSelectorIMP = (void (*)(id, SEL))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD); + + _QMUITransitionNavigationBar *navigationBar = (_QMUITransitionNavigationBar *)selfObject.superview; + if (selfObject.window != nil && [navigationBar isKindOfClass:_QMUITransitionNavigationBar.class]) { + [navigationBar updateLayout]; + } + }; + }); + #ifdef IOS15_SDK_ALLOWED if (@available(iOS 15.0, *)) { // - [UINavigationBar _didMoveFromWindow:toWindow:] @@ -255,9 +272,11 @@ - (void)setOriginalNavigationBar:(UINavigationBar *)originBar { } - (void)layoutSubviews { - [super layoutSubviews]; - // 实测 iOS 11 Beta 1-5 里,自己 init 的 navigationBar.backgroundView.height 默认一直是 44,所以才加上这个兼容 - self.qmui_backgroundView.frame = self.bounds; + [CALayer qmui_performWithoutAnimation:^{ + [super layoutSubviews]; + // 实测 iOS 11 Beta 1-5 里,自己 init 的 navigationBar.backgroundView.height 默认一直是 44,所以才加上这个兼容 + self.qmui_backgroundView.frame = self.bounds; + }]; } // NavBarRemoveBackgroundEffectAutomatically 在开启了 AutomaticCustomNavigationBarTransitionStyle 时可能对假 bar 无效 @@ -274,7 +293,9 @@ - (void)updateLayout { [self.parentViewController.view bringSubviewToFront:self]; UIView *backgroundView = self.originalNavigationBar.qmui_backgroundView; CGRect rect = [backgroundView.superview convertRect:backgroundView.frame toView:self.parentViewController.view]; - self.frame = CGRectSetX(rect, 0);// push/pop 过程中系统的导航栏转换过来的 x 可能是 112、-112 + [CALayer qmui_performWithoutAnimation:^{ + self.frame = CGRectSetX(rect, 0); // push/pop 过程中系统的导航栏转换过来的 x 可能是 112、-112 + }]; } } diff --git a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m index 21982331..308f263f 100644 --- a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m +++ b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationController+NavigationBarTransition.m @@ -231,6 +231,8 @@ - (void)addTransitionNavigationBarAndBindNavigationBar:(BOOL)shouldBind { } _QMUITransitionNavigationBar *customBar = [[_QMUITransitionNavigationBar alloc] init]; + /// iOS 26不设置items时子视图不会添加 + customBar.items = @[[[UINavigationItem alloc] initWithTitle:@""]]; customBar.parentViewController = self; self.transitionNavigationBar = customBar; diff --git a/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m b/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m index 9dbb0490..38a93d8f 100644 --- a/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m +++ b/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m @@ -87,6 +87,7 @@ - (void)setQmui_badgeString:(NSString *)qmui_badgeString { [self updateViewDidSetBlockIfNeeded]; } self.qmui_view.qmui_badgeString = qmui_badgeString; + self.qmui_selectedView.qmui_badgeString = qmui_badgeString; } - (NSString *)qmui_badgeString { @@ -97,6 +98,7 @@ - (NSString *)qmui_badgeString { - (void)setQmui_badgeBackgroundColor:(UIColor *)qmui_badgeBackgroundColor { objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeBackgroundColor, qmui_badgeBackgroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_badgeBackgroundColor = qmui_badgeBackgroundColor; + self.qmui_selectedView.qmui_badgeBackgroundColor = qmui_badgeBackgroundColor; } - (UIColor *)qmui_badgeBackgroundColor { @@ -107,6 +109,7 @@ - (UIColor *)qmui_badgeBackgroundColor { - (void)setQmui_badgeTextColor:(UIColor *)qmui_badgeTextColor { objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeTextColor, qmui_badgeTextColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_badgeTextColor = qmui_badgeTextColor; + self.qmui_selectedView.qmui_badgeTextColor = qmui_badgeTextColor; } - (UIColor *)qmui_badgeTextColor { @@ -117,6 +120,7 @@ - (UIColor *)qmui_badgeTextColor { - (void)setQmui_badgeFont:(UIFont *)qmui_badgeFont { objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeFont, qmui_badgeFont, OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_badgeFont = qmui_badgeFont; + self.qmui_selectedView.qmui_badgeFont = qmui_badgeFont; } - (UIFont *)qmui_badgeFont { @@ -127,6 +131,7 @@ - (UIFont *)qmui_badgeFont { - (void)setQmui_badgeContentEdgeInsets:(UIEdgeInsets)qmui_badgeContentEdgeInsets { objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeContentEdgeInsets, [NSValue valueWithUIEdgeInsets:qmui_badgeContentEdgeInsets], OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_badgeContentEdgeInsets = qmui_badgeContentEdgeInsets; + self.qmui_selectedView.qmui_badgeContentEdgeInsets = qmui_badgeContentEdgeInsets; } - (UIEdgeInsets)qmui_badgeContentEdgeInsets { @@ -137,6 +142,7 @@ - (UIEdgeInsets)qmui_badgeContentEdgeInsets { - (void)setQmui_badgeOffset:(CGPoint)qmui_badgeOffset { objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeOffset, @(qmui_badgeOffset), OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_badgeOffset = qmui_badgeOffset; + self.qmui_selectedView.qmui_badgeOffset = qmui_badgeOffset; } - (CGPoint)qmui_badgeOffset { @@ -147,6 +153,7 @@ - (CGPoint)qmui_badgeOffset { - (void)setQmui_badgeOffsetLandscape:(CGPoint)qmui_badgeOffsetLandscape { objc_setAssociatedObject(self, &kAssociatedObjectKey_badgeOffsetLandscape, @(qmui_badgeOffsetLandscape), OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_badgeOffsetLandscape = qmui_badgeOffsetLandscape; + self.qmui_selectedView.qmui_badgeOffsetLandscape = qmui_badgeOffsetLandscape; } - (CGPoint)qmui_badgeOffsetLandscape { @@ -155,6 +162,7 @@ - (CGPoint)qmui_badgeOffsetLandscape { - (void)setQmui_badgeView:(__kindof UIView *)qmui_badgeView { self.qmui_view.qmui_badgeView = qmui_badgeView; + self.qmui_selectedView.qmui_badgeView = qmui_badgeView; } - (__kindof UIView *)qmui_badgeView { @@ -163,6 +171,7 @@ - (__kindof UIView *)qmui_badgeView { - (void)setQmui_badgeViewDidLayoutBlock:(void (^)(__kindof UIView * _Nonnull, __kindof UIView * _Nonnull))qmui_badgeViewDidLayoutBlock { self.qmui_view.qmui_badgeViewDidLayoutBlock = qmui_badgeViewDidLayoutBlock; + self.qmui_selectedView.qmui_badgeViewDidLayoutBlock = qmui_badgeViewDidLayoutBlock; } - (void (^)(__kindof UIView * _Nonnull, __kindof UIView * _Nonnull))qmui_badgeViewDidLayoutBlock { @@ -242,23 +251,75 @@ - (void)setQmui_updatesIndicatorViewDidLayoutBlock:(void (^)(__kindof UIView * _ #pragma mark - Common +- (nullable UIView *)qmui_selectedView { + if (QMUIHelper.isUsedLiquidGlass) { + if (![self isKindOfClass:UITabBarItem.class]) { + return nil; + } + UIView *view = self.qmui_view; + if (!view) { + return nil; + } + NSInteger index = [view.superview.subviews indexOfObject:view]; + if (index == NSNotFound) { + return nil; + } + UIView *platterView = view.superview.superview; + if (![NSStringFromClass(platterView.class) hasSuffix:@"_UITabBarPlatterView"]) { + return nil; + } + UIView *selectedContentView = platterView.subviews.firstObject; + if (![NSStringFromClass(selectedContentView.class) hasSuffix:@"SelectedContentView"]) { + return nil; + } + if (index < selectedContentView.subviews.count) { + UIView *selectedView = [selectedContentView.subviews objectAtIndex:index]; + return selectedView; + } + } + return nil; +} + - (void)updateViewDidSetBlockIfNeeded { if (!self.qmui_viewDidSetBlock) { self.qmui_viewDidSetBlock = ^(__kindof UIBarItem * _Nonnull item, UIView * _Nullable view) { + UIView *selectedView = item.qmui_selectedView; + view.qmui_badgeBackgroundColor = item.qmui_badgeBackgroundColor; + selectedView.qmui_badgeBackgroundColor = item.qmui_badgeBackgroundColor; + view.qmui_badgeTextColor = item.qmui_badgeTextColor; + selectedView.qmui_badgeTextColor = item.qmui_badgeTextColor; + view.qmui_badgeFont = item.qmui_badgeFont; + selectedView.qmui_badgeFont = item.qmui_badgeFont; + view.qmui_badgeContentEdgeInsets = item.qmui_badgeContentEdgeInsets; + selectedView.qmui_badgeContentEdgeInsets = item.qmui_badgeContentEdgeInsets; + view.qmui_badgeOffset = item.qmui_badgeOffset; + selectedView.qmui_badgeOffset = item.qmui_badgeOffset; + view.qmui_badgeOffsetLandscape = item.qmui_badgeOffsetLandscape; + selectedView.qmui_badgeOffsetLandscape = item.qmui_badgeOffsetLandscape; view.qmui_updatesIndicatorColor = item.qmui_updatesIndicatorColor; + selectedView.qmui_updatesIndicatorColor = item.qmui_updatesIndicatorColor; + view.qmui_updatesIndicatorSize = item.qmui_updatesIndicatorSize; + selectedView.qmui_updatesIndicatorSize = item.qmui_updatesIndicatorSize; + view.qmui_updatesIndicatorOffset = item.qmui_updatesIndicatorOffset; + selectedView.qmui_updatesIndicatorOffset = item.qmui_updatesIndicatorOffset; + view.qmui_updatesIndicatorOffsetLandscape = item.qmui_updatesIndicatorOffsetLandscape; + selectedView.qmui_updatesIndicatorOffsetLandscape = item.qmui_updatesIndicatorOffsetLandscape; view.qmui_badgeString = item.qmui_badgeString; + selectedView.qmui_badgeString = item.qmui_badgeString; + view.qmui_shouldShowUpdatesIndicator = item.qmui_shouldShowUpdatesIndicator; + selectedView.qmui_shouldShowUpdatesIndicator = item.qmui_shouldShowUpdatesIndicator; }; // 为 qmui_viewDidSetBlock 赋值前 item 已经 set 完 view,则手动触发一次 diff --git a/QMUIKit/QMUIComponents/QMUIBadge/UIView+QMUIBadge.m b/QMUIKit/QMUIComponents/QMUIBadge/UIView+QMUIBadge.m index 1bb929a5..e6a3744a 100644 --- a/QMUIKit/QMUIComponents/QMUIBadge/UIView+QMUIBadge.m +++ b/QMUIKit/QMUIComponents/QMUIBadge/UIView+QMUIBadge.m @@ -322,7 +322,7 @@ - (void)updateLayoutSubviewsBlockIfNeeded { // 不管 image 还是 text 的 UIBarButtonItem 都获取内部的 _UIModernBarButton 即可 - (UIView *)findBarButtonContentView { NSString *classString = NSStringFromClass(self.class); - if ([classString isEqualToString:@"UITabBarButton"]) { + if ([classString isEqualToString:@"UITabBarButton"] || [classString isEqualToString:@"_UITabButton"]) { // 特别的,对于 UITabBarItem,将 imageView 作为参考 view UIView *imageView = [UITabBarItem qmui_imageViewInTabBarButton:self]; return imageView; diff --git a/QMUIKit/QMUIComponents/QMUIButton/QMUINavigationButton.m b/QMUIKit/QMUIComponents/QMUIButton/QMUINavigationButton.m index b79bdebb..58cb16b5 100644 --- a/QMUIKit/QMUIComponents/QMUIButton/QMUINavigationButton.m +++ b/QMUIKit/QMUIComponents/QMUIButton/QMUINavigationButton.m @@ -99,7 +99,11 @@ - (void)renderButtonStyle { break; case QMUINavigationButtonTypeImage: // 拓展宽度,以保证用 leftBarButtonItems/rightBarButtonItems 时,按钮与按钮之间间距与系统的保持一致 - self.contentEdgeInsets = UIEdgeInsetsMake(0, 11, 0, 11); + if (QMUIHelper.isUsedLiquidGlass) { + self.contentEdgeInsets = UIEdgeInsetsZero; + } else { + self.contentEdgeInsets = UIEdgeInsetsMake(0, 11, 0, 11); + } break; case QMUINavigationButtonTypeBold: { font = NavBarButtonFontBold; @@ -122,14 +126,18 @@ - (void)renderButtonStyle { self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft; - // @warning 这些数值都是每个iOS版本核对过没问题的,如果修改则要检查要每个版本里与系统UIBarButtonItem的布局是否一致 - UIOffset titleOffsetBaseOnSystem = UIOffsetMake(6, 0);// 经过这些数值的调整后,自定义返回按钮的位置才能和系统默认返回按钮的位置对准,而配置表里设置的值是在这个调整的基础上再调整 - UIOffset configurationOffset = NavBarBarBackButtonTitlePositionAdjustment; - self.titleEdgeInsets = UIEdgeInsetsMake(titleOffsetBaseOnSystem.vertical + configurationOffset.vertical, titleOffsetBaseOnSystem.horizontal + configurationOffset.horizontal, -titleOffsetBaseOnSystem.vertical - configurationOffset.vertical, -titleOffsetBaseOnSystem.horizontal - configurationOffset.horizontal); - self.contentEdgeInsets = UIEdgeInsetsMake(0, - 0, - 0, - self.titleEdgeInsets.left); + if (QMUIHelper.isUsedLiquidGlass) { + self.contentEdgeInsets = UIEdgeInsetsZero; + } else { + // @warning 这些数值都是每个iOS版本核对过没问题的,如果修改则要检查要每个版本里与系统UIBarButtonItem的布局是否一致 + UIOffset titleOffsetBaseOnSystem = UIOffsetMake(6, 0);// 经过这些数值的调整后,自定义返回按钮的位置才能和系统默认返回按钮的位置对准,而配置表里设置的值是在这个调整的基础上再调整 + UIOffset configurationOffset = NavBarBarBackButtonTitlePositionAdjustment; + self.titleEdgeInsets = UIEdgeInsetsMake(titleOffsetBaseOnSystem.vertical + configurationOffset.vertical, titleOffsetBaseOnSystem.horizontal + configurationOffset.horizontal, -titleOffsetBaseOnSystem.vertical - configurationOffset.vertical, -titleOffsetBaseOnSystem.horizontal - configurationOffset.horizontal); + self.contentEdgeInsets = UIEdgeInsetsMake(0, + 0, + 0, + self.titleEdgeInsets.left); + } } break; @@ -207,29 +215,31 @@ - (void)tintColorDidChange { // 对按钮内容添加偏移,让UIBarButtonItem适配最新设备的系统行为,统一位置。注意 iOS 11 及以后,只有 image 类型的才会走进来 - (UIEdgeInsets)alignmentRectInsets { - - UIEdgeInsets insets = [super alignmentRectInsets]; - - if (self.type == QMUINavigationButtonTypeNormal || self.type == QMUINavigationButtonTypeBold) { - // 对于奇数大小的字号,不同 iOS 版本的偏移策略不同,统一一下 - if (self.titleLabel.font.pointSize / 2.0 > 0) { - insets.top = -PixelOne; - insets.bottom = PixelOne; - } - } else if (self.type == QMUINavigationButtonTypeImage) { - // 图片类型的按钮,分别对最左、最右那个按钮调整 inset(这里与 UINavigationItem(QMUINavigationButton) 里的 position 赋值配合使用) - if (self.buttonPosition == QMUINavigationButtonPositionLeft) { - insets.left = 11; - } else if (self.buttonPosition == QMUINavigationButtonPositionRight) { - insets.right = 11; + if (QMUIHelper.isUsedLiquidGlass) { + return [super alignmentRectInsets]; + } else { + UIEdgeInsets insets = [super alignmentRectInsets]; + if (self.type == QMUINavigationButtonTypeNormal || self.type == QMUINavigationButtonTypeBold) { + // 对于奇数大小的字号,不同 iOS 版本的偏移策略不同,统一一下 + if (self.titleLabel.font.pointSize / 2.0 > 0) { + insets.top = -PixelOne; + insets.bottom = PixelOne; + } + } else if (self.type == QMUINavigationButtonTypeImage) { + // 图片类型的按钮,分别对最左、最右那个按钮调整 inset(这里与 UINavigationItem(QMUINavigationButton) 里的 position 赋值配合使用) + if (self.buttonPosition == QMUINavigationButtonPositionLeft) { + insets.left = 11; + } else if (self.buttonPosition == QMUINavigationButtonPositionRight) { + insets.right = 11; + } + + insets.top = 1; + } else if (self.type == QMUINavigationButtonTypeBack) { + insets.top = PixelOne; } - insets.top = 1; - } else if (self.type == QMUINavigationButtonTypeBack) { - insets.top = PixelOne; + return insets; } - - return insets; } @end @@ -492,7 +502,7 @@ + (void)load { // result 有值意味着该事件本应属于 bar 的,这时候才干预。 // 属于 bar 但又分配给容器而不是精准的某个内容 view,此时才考虑扩大点击范围的识别。 - BOOL hitNothing = result == selfObject.qmui_contentView || [NSStringFromClass(result.class) containsString:@"StackView"]; + BOOL hitNothing = result == selfObject.qmui_contentView || ([NSStringFromClass(result.class) containsString:@"StackView"] && result.superview == selfObject.qmui_contentView); if (!hitNothing) return result; NSMutableArray *customViews = [[NSMutableArray alloc] init]; diff --git a/QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m b/QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m index 5e7ed03f..679bda32 100644 --- a/QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m +++ b/QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m @@ -20,6 +20,7 @@ #import "UIWindow+QMUI.h" #import "UIColor+QMUI.h" #import "QMUITextView.h" +#import "UIApplication+QMUI.h" /// 定义一个 class 只是为了在 Lookin 里表达这是一个 console window 而已,不需要实现什么东西 @interface QMUIConsoleWindow : UIWindow @@ -27,19 +28,30 @@ @interface QMUIConsoleWindow : UIWindow @implementation QMUIConsoleWindow -- (instancetype)init { - if (self = [super init]) { - self.backgroundColor = nil; - if (QMUICMIActivated) { - self.windowLevel = UIWindowLevelQMUIConsole; - } else { - self.windowLevel = 1; - } - self.qmui_capturesStatusBarAppearance = NO; +- (instancetype)initWithWindowScene:(UIWindowScene *)windowScene { + if (self = [super initWithWindowScene:windowScene]) { + [self didInitialize]; } return self; } +- (instancetype)initWithFrame:(CGRect)frame { + if (self = [super initWithFrame:frame]) { + [self didInitialize]; + } + return self; +} + +- (void)didInitialize { + self.backgroundColor = nil; + if (QMUICMIActivated) { + self.windowLevel = UIWindowLevelQMUIConsole; + } else { + self.windowLevel = 1; + } + self.qmui_capturesStatusBarAppearance = NO; +} + - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { // 当显示 QMUIConsole 时,点击空白区域,consoleViewController hitTest 会 return nil,从而将事件传递给 window,再由 window hitTest return nil 来把事件传递给 UIApplication.delegate.window。但在 iPad 12-inch 里,当 consoleViewController hitTest return nil 后,事件会错误地传递给 consoleViewController.view.superview(而不是 consoleWindow),不清楚原因,暂时做一下保护 // https://github.com/Tencent/QMUI_iOS/issues/1169 @@ -134,7 +146,7 @@ + (void)hide { - (void)initConsoleWindowIfNeeded { if (!self.consoleWindow) { - self.consoleWindow = [[QMUIConsoleWindow alloc] init]; + self.consoleWindow = [QMUIConsoleWindow qmui_windowWithWindowScene:UIApplication.sharedApplication.qmui_delegateWindow.windowScene]; self.consoleViewController = [[QMUIConsoleViewController alloc] init]; self.consoleWindow.rootViewController = self.consoleViewController; } diff --git a/QMUIKit/QMUIComponents/QMUIKeyboardManager.m b/QMUIKit/QMUIComponents/QMUIKeyboardManager.m index 178df3ec..8bd32f5a 100644 --- a/QMUIKit/QMUIComponents/QMUIKeyboardManager.m +++ b/QMUIKit/QMUIComponents/QMUIKeyboardManager.m @@ -20,6 +20,7 @@ #import "QMUIMultipleDelegates.h" #import "NSArray+QMUI.h" #import "UIView+QMUI.h" +#import "UIApplication+QMUI.h" @class QMUIKeyboardViewFrameObserver; @protocol QMUIKeyboardViewFrameObserverDelegate @@ -396,10 +397,10 @@ - (UIResponder *)unPackageTargetResponder:(NSValue *)value { } - (UIResponder *)firstResponderInWindows { - UIResponder *responder = [UIApplication.sharedApplication.keyWindow qmui_findFirstResponder]; + UIResponder *responder = [UIApplication.sharedApplication.qmui_keyWindow qmui_findFirstResponder]; if (!responder) { - for (UIWindow *window in UIApplication.sharedApplication.windows) { - if (window != UIApplication.sharedApplication.keyWindow) { + for (UIWindow *window in UIApplication.sharedApplication.qmui_windows) { + if (window != UIApplication.sharedApplication.qmui_keyWindow) { responder = [window qmui_findFirstResponder]; if (responder) { return responder; @@ -709,7 +710,7 @@ - (void)keyboardDidChangedFrame:(UIView *)keyboardView { keyboardMoveUserInfo.animationOptions = self.lastUserInfo ? self.lastUserInfo.animationOptions : keyboardMoveUserInfo.animationCurve<<16; keyboardMoveUserInfo.beginFrame = self.keyboardMoveBeginRect; keyboardMoveUserInfo.endFrame = endFrame; - keyboardMoveUserInfo.isFloatingKeyboard = keyboardView ? CGRectGetWidth(keyboardView.bounds) < CGRectGetWidth(UIApplication.sharedApplication.delegate.window.bounds) : NO; + keyboardMoveUserInfo.isFloatingKeyboard = keyboardView ? CGRectGetWidth(keyboardView.bounds) < CGRectGetWidth(UIApplication.sharedApplication.qmui_delegateWindow.bounds) : NO; if (self.debug) { NSLog(@"keyboardDidMoveNotification - %@\n", self); @@ -720,7 +721,7 @@ - (void)keyboardDidChangedFrame:(UIView *)keyboardView { self.keyboardMoveBeginRect = endFrame; if (self.currentResponder) { - UIWindow *mainWindow = UIApplication.sharedApplication.keyWindow ?: UIApplication.sharedApplication.delegate.window; + UIWindow *mainWindow = UIApplication.sharedApplication.qmui_keyWindow ?: UIApplication.sharedApplication.qmui_delegateWindow; if (mainWindow) { CGRect keyboardRect = keyboardMoveUserInfo.endFrame; CGFloat distanceFromBottom = [QMUIKeyboardManager distanceFromMinYToBottomInView:mainWindow keyboardRect:keyboardRect]; @@ -793,7 +794,7 @@ + (CGRect)convertKeyboardRect:(CGRect)rect toView:(UIView *)view { return rect; } - UIWindow *mainWindow = UIApplication.sharedApplication.keyWindow ?: UIApplication.sharedApplication.delegate.window; + UIWindow *mainWindow = UIApplication.sharedApplication.qmui_keyWindow ?: UIApplication.sharedApplication.qmui_delegateWindow; if (!mainWindow) { if (view) { [view convertRect:rect fromView:nil]; @@ -855,7 +856,7 @@ + (CGFloat)distanceFromMinYToBottomInView:(UIView *)view keyboardRect:(CGRect)re 所以只要找到 UIInputSetHostView 即可,优先从 UIRemoteKeyboardWindow 找,不存在的话则从 UITextEffectsWindow 找。 */ + (UIView *)keyboardView { - UIView *inputSetHostView = [[UIApplication.sharedApplication.windows qmui_filterWithBlock:^BOOL(__kindof UIWindow * _Nonnull window) { + UIView *inputSetHostView = [[UIApplication.sharedApplication.qmui_windows qmui_filterWithBlock:^BOOL(__kindof UIWindow * _Nonnull window) { return [NSStringFromClass(window.class) isEqualToString:@"UIRemoteKeyboardWindow"]; }] qmui_compactMapWithBlock:^id _Nullable(__kindof UIWindow * _Nonnull window) { return [self inputSetHostViewInWindow:window]; @@ -863,7 +864,7 @@ + (UIView *)keyboardView { if (inputSetHostView) return inputSetHostView; - inputSetHostView = [[UIApplication.sharedApplication.windows qmui_filterWithBlock:^BOOL(__kindof UIWindow * _Nonnull window) { + inputSetHostView = [[UIApplication.sharedApplication.qmui_windows qmui_filterWithBlock:^BOOL(__kindof UIWindow * _Nonnull window) { return [NSStringFromClass(window.class) isEqualToString:@"UITextEffectsWindow"]; }] qmui_compactMapWithBlock:^id _Nullable(__kindof UIWindow * _Nonnull window) { return [self inputSetHostViewInWindow:window]; @@ -885,14 +886,14 @@ + (UIWindow *)keyboardWindow { UIView *inputSetHostView = [self keyboardView]; if (inputSetHostView) return inputSetHostView.window; - UIWindow *window = [UIApplication.sharedApplication.windows qmui_firstMatchWithBlock:^BOOL(__kindof UIWindow * _Nonnull item) { + UIWindow *window = [UIApplication.sharedApplication.qmui_windows qmui_firstMatchWithBlock:^BOOL(__kindof UIWindow * _Nonnull item) { return [NSStringFromClass(item.class) isEqualToString:@"UIRemoteKeyboardWindow"]; }]; if (window) { return window; } - window = [UIApplication.sharedApplication.windows qmui_firstMatchWithBlock:^BOOL(__kindof UIWindow * _Nonnull item) { + window = [UIApplication.sharedApplication.qmui_windows qmui_firstMatchWithBlock:^BOOL(__kindof UIWindow * _Nonnull item) { return [NSStringFromClass(item.class) isEqualToString:@"UITextEffectsWindow"]; }]; return window; @@ -929,7 +930,7 @@ + (CGFloat)visibleKeyboardHeight { // iPad“侧拉”模式打开的 App,App Window 和键盘 Window 尺寸不同,如果以键盘 Window 为准则会认为键盘一直在屏幕上,从而出现误判,所以这里改为用 App Window。 // iPhone、iPad 全屏/分屏/台前调度,都没这个问题 // UIWindow *keyboardWindow = keyboardView.window; - UIWindow *keyboardWindow = UIApplication.sharedApplication.delegate.window; + UIWindow *keyboardWindow = UIApplication.sharedApplication.qmui_delegateWindow; if (!keyboardView || !keyboardWindow) { return 0; } else { diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h index b44011fa..7294f359 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h @@ -314,7 +314,7 @@ typedef NS_ENUM(NSUInteger, QMUIModalPresentationAnimationStyle) { @end -/// 专用于QMUIModalPresentationViewController的UIWindow,这样才能在`UIApplication.sharedApplication.windows`里方便地区分出来 +/// 专用于QMUIModalPresentationViewController的UIWindow,这样才能在`UIApplication.sharedApplication.qmui_windows`里方便地区分出来 @interface QMUIModalPresentationWindow : UIWindow @end diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m index 5355412f..9c3d52dc 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m @@ -20,6 +20,7 @@ #import "QMUIKeyboardManager.h" #import "UIWindow+QMUI.h" #import "QMUIAppearance.h" +#import "UIApplication+QMUI.h" @interface UIViewController () @@ -271,10 +272,10 @@ - (void)viewWillDisappear:(BOOL)animated { if (self.shownInWindowMode) { // 恢复 keyWindow 之前做一下检查,避免这个问题 https://github.com/Tencent/QMUI_iOS/issues/90 - if (UIApplication.sharedApplication.keyWindow == self.window) { + if (UIApplication.sharedApplication.qmui_keyWindow == self.window) { if (self.previousKeyWindow.hidden) { // 保护了这个 issue 记录的情况,避免主 window 丢失 keyWindow https://github.com/Tencent/QMUI_iOS/issues/315 - [UIApplication.sharedApplication.delegate.window makeKeyWindow]; + [UIApplication.sharedApplication.qmui_delegateWindow makeKeyWindow]; } else { [self.previousKeyWindow makeKeyWindow]; } @@ -505,9 +506,9 @@ - (void)showWithAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { // makeKeyAndVisible 导致的 viewWillAppear: 必定 animated 是 NO 的,所以这里用额外的变量保存这个 animated 的值 self.appearAnimated = animated; self.appearCompletionBlock = completion; - self.previousKeyWindow = UIApplication.sharedApplication.keyWindow; + self.previousKeyWindow = UIApplication.sharedApplication.qmui_keyWindow; if (!self.window) { - self.window = [[QMUIModalPresentationWindow alloc] init]; + self.window = [QMUIModalPresentationWindow qmui_windowWithWindowScene:UIApplication.sharedApplication.qmui_delegateWindow.windowScene]; self.window.windowLevel = UIWindowLevelQMUIAlertView; self.window.backgroundColor = UIColorClear;// 避免横竖屏旋转时出现黑色 [self updateWindowStatusBarCapture]; @@ -751,7 +752,7 @@ - (UIViewController *)childViewControllerForHomeIndicatorAutoHidden { @implementation QMUIModalPresentationViewController (Manager) + (BOOL)isAnyModalPresentationViewControllerVisible { - for (UIWindow *window in UIApplication.sharedApplication.windows) { + for (UIWindow *window in UIApplication.sharedApplication.qmui_windows) { if ([window isKindOfClass:[QMUIModalPresentationWindow class]] && !window.hidden) { return YES; } @@ -763,7 +764,7 @@ + (BOOL)hideAllVisibleModalPresentationViewControllerIfCan { BOOL hideAllFinally = YES; - for (UIWindow *window in UIApplication.sharedApplication.windows) { + for (UIWindow *window in UIApplication.sharedApplication.qmui_windows) { if (![window isKindOfClass:[QMUIModalPresentationWindow class]]) { continue; } diff --git a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m index dd76738c..6b491cdc 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m +++ b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m @@ -24,6 +24,7 @@ #import "QMUIAppearance.h" #import "CALayer+QMUI.h" #import "NSShadow+QMUI.h" +#import "UIApplication+QMUI.h" @interface QMUIPopupContainerViewWindow : UIWindow @@ -48,7 +49,6 @@ @interface QMUIPopupContainerView () { } @property(nonatomic, strong) QMUIPopupContainerViewWindow *popupWindow; -@property(nonatomic, weak) UIWindow *previousKeyWindow; @property(nonatomic, assign) BOOL hidesByUserTap; @end @@ -379,7 +379,7 @@ - (void)setSourceBarItem:(__kindof UIBarItem *)sourceBarItem { // 每次都要重新定义 block,否则当不同的 popup 在同一个 sourceBarItem 显示,这个 block 内部得到的 weakSelf 可能是前一次的 sourceBarItem.qmui_viewLayoutDidChangeBlock = ^(__kindof UIBarItem * _Nonnull item, UIView * _Nullable view) { if (!view.window || !weakSelf.superview) return; - UIView *convertToView = weakSelf.popupWindow ? UIApplication.sharedApplication.delegate.window : weakSelf.superview;// 对于以 window 方式显示的情况,由于横竖屏旋转时,不同 window 的旋转顺序不同,所以可能导致 sourceBarItem 所在的 window 已经旋转了但 popupWindow 还没旋转(iOS 11 及以后),那么计算出来的坐标就错了,所以这里改为用 UIApplication window + UIView *convertToView = weakSelf.popupWindow ? UIApplication.sharedApplication.qmui_delegateWindow : weakSelf.superview;// 对于以 window 方式显示的情况,由于横竖屏旋转时,不同 window 的旋转顺序不同,所以可能导致 sourceBarItem 所在的 window 已经旋转了但 popupWindow 还没旋转(iOS 11 及以后),那么计算出来的坐标就错了,所以这里改为用 UIApplication window CGRect rect = [view qmui_convertRect:view.bounds toView:convertToView]; weakSelf.sourceRect = rect; }; @@ -399,7 +399,7 @@ - (void)setSourceView:(__kindof UIView *)sourceView { __weak __typeof(self)weakSelf = self; sourceView.qmui_frameDidChangeBlock = ^(__kindof UIView * _Nonnull view, CGRect precedingFrame) { if (!view.window || !weakSelf.superview) return; - UIView *convertToView = weakSelf.popupWindow ? UIApplication.sharedApplication.delegate.window : weakSelf.superview;// 对于以 window 方式显示的情况,由于横竖屏旋转时,不同 window 的旋转顺序不同,所以可能导致 sourceBarItem 所在的 window 已经旋转了但 popupWindow 还没旋转(iOS 11 及以后),那么计算出来的坐标就错了,所以这里改为用 UIApplication window + UIView *convertToView = weakSelf.popupWindow ? UIApplication.sharedApplication.qmui_delegateWindow : weakSelf.superview;// 对于以 window 方式显示的情况,由于横竖屏旋转时,不同 window 的旋转顺序不同,所以可能导致 sourceBarItem 所在的 window 已经旋转了但 popupWindow 还没旋转(iOS 11 及以后),那么计算出来的坐标就错了,所以这里改为用 UIApplication window CGRect rect = [view qmui_convertRect:view.bounds toView:convertToView]; weakSelf.sourceRect = rect; }; @@ -697,8 +697,7 @@ - (void)showWithAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { QMUICommonViewController *viewController = (QMUICommonViewController *)self.popupWindow.rootViewController; viewController.supportedOrientationMask = [QMUIHelper visibleViewController].supportedInterfaceOrientations; - self.previousKeyWindow = UIApplication.sharedApplication.keyWindow; - [self.popupWindow makeKeyAndVisible]; + self.popupWindow.hidden = NO; isShowingByWindowMode = YES; } else { @@ -797,16 +796,12 @@ - (void)hideWithAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - (void)hideCompletionWithWindowMode:(BOOL)windowMode completion:(void (^)(BOOL))completion { if (windowMode) { - // 恢复 keyWindow 之前做一下检查,避免类似问题 https://github.com/Tencent/QMUI_iOS/issues/90 - if (UIApplication.sharedApplication.keyWindow == self.popupWindow) { - [self.previousKeyWindow makeKeyWindow]; - } - // iOS 9 下(iOS 8 和 10 都没问题)需要主动移除,才能令 rootViewController 和 popupWindow 立即释放,不影响后续的 layout 判断,如果不加这两句,虽然 popupWindow 指针被置为 nil,但其实对象还存在,View 层级关系也还在 // https://github.com/Tencent/QMUI_iOS/issues/75 [self removeFromSuperview]; self.popupWindow.rootViewController = nil; + self.popupWindow.windowScene = nil; self.popupWindow.hidden = YES; self.popupWindow = nil; } else { @@ -835,7 +830,7 @@ - (BOOL)isSubviewShowing:(UIView *)subview { - (void)initPopupContainerViewWindowIfNeeded { if (!self.popupWindow) { - self.popupWindow = [[QMUIPopupContainerViewWindow alloc] init]; + self.popupWindow = [QMUIPopupContainerViewWindow qmui_windowWithWindowScene:UIApplication.sharedApplication.qmui_delegateWindow.windowScene]; self.popupWindow.qmui_capturesStatusBarAppearance = NO; self.popupWindow.backgroundColor = UIColorClear; self.popupWindow.windowLevel = UIWindowLevelQMUIAlertView; diff --git a/QMUIKit/QMUIComponents/QMUISearchController.m b/QMUIKit/QMUIComponents/QMUISearchController.m index 0a77d8c2..b54124c5 100644 --- a/QMUIKit/QMUIComponents/QMUISearchController.m +++ b/QMUIKit/QMUIComponents/QMUISearchController.m @@ -26,6 +26,7 @@ #import "UIViewController+QMUI.h" #import "UISearchController+QMUI.h" #import "UIGestureRecognizer+QMUI.h" +#import "UIApplication+QMUI.h" BeginIgnoreDeprecatedWarning @@ -250,7 +251,7 @@ - (void)handleSwipe:(UIScreenEdgePanGestureRecognizer *)gestureRecognizer { self.dismissBySwipe = YES; // 盖到最上面,挡住退出搜索过程中可能出现的界面闪烁 [self.snapshotView removeFromSuperview]; - [UIApplication.sharedApplication.delegate.window addSubview:self.snapshotView]; + [UIApplication.sharedApplication.qmui_delegateWindow addSubview:self.snapshotView]; QMUILogInfo(@"QMUISearchController", @"swipeGesture snapshot change superview to window"); self.active = NO; self.searchController.view.transform = CGAffineTransformIdentity; @@ -285,7 +286,7 @@ - (void)createSnapshotObjects { self.snapshotMaskView = [[UIView alloc] init]; self.snapshotMaskView.backgroundColor = [UIColor.blackColor colorWithAlphaComponent:.1]; } - self.snapshotView = [UIApplication.sharedApplication.delegate.window snapshotViewAfterScreenUpdates:NO]; + self.snapshotView = [UIApplication.sharedApplication.qmui_delegateWindow snapshotViewAfterScreenUpdates:NO]; self.snapshotMaskView.frame = self.snapshotView.bounds; [self.snapshotView addSubview:self.snapshotMaskView]; if (!self.swipeGestureRecognizer) { @@ -293,7 +294,7 @@ - (void)createSnapshotObjects { self.swipeGestureRecognizer.edges = UIRectEdgeLeft; self.swipeGestureRecognizer.delegate = self; } - [UIApplication.sharedApplication.delegate.window addGestureRecognizer:self.swipeGestureRecognizer]; + [UIApplication.sharedApplication.qmui_delegateWindow addGestureRecognizer:self.swipeGestureRecognizer]; } - (void)resetSnapshotObjects { @@ -305,7 +306,7 @@ - (void)cleanSnapshotObjects { [self.snapshotView removeFromSuperview]; [self.snapshotMaskView removeFromSuperview]; self.snapshotView = nil; - [UIApplication.sharedApplication.delegate.window removeGestureRecognizer:self.swipeGestureRecognizer]; + [UIApplication.sharedApplication.qmui_delegateWindow removeGestureRecognizer:self.swipeGestureRecognizer]; QMUILogInfo(@"QMUISearchController", @"swipeGesture clean all objects"); } diff --git a/QMUIKit/QMUIComponents/QMUITextView.m b/QMUIKit/QMUIComponents/QMUITextView.m index 0f319661..d7e50119 100644 --- a/QMUIKit/QMUIComponents/QMUITextView.m +++ b/QMUIKit/QMUIComponents/QMUITextView.m @@ -25,9 +25,6 @@ /// 系统 textView 默认的字号大小,用于 placeholder 默认的文字大小。实测得到,请勿修改。 const CGFloat kSystemTextViewDefaultFontPointSize = 12.0f; -/// 当系统的 textView.textContainerInset 为 UIEdgeInsetsZero 时,文字与 textView 边缘的间距。实测得到,请勿修改(在输入框font大于13时准确,小于等于12时,y有-1px的偏差)。 -const UIEdgeInsets kSystemTextViewFixTextInsets = {0, 5, 0, 5}; - // 私有的类,专用于实现 QMUITextViewDelegate,避免 self.delegate = self 的写法(以前是 QMUITextView 自己实现了 delegate) @interface _QMUITextViewDelegator : NSObject @@ -304,7 +301,16 @@ - (CGSize)sizeThatFits:(CGSize)size { } - (UIEdgeInsets)allInsets { - return UIEdgeInsetsConcat(UIEdgeInsetsConcat(UIEdgeInsetsConcat(self.textContainerInset, self.placeholderMargins), kSystemTextViewFixTextInsets), self.adjustedContentInset); + CGFloat padding = self.textContainer.lineFragmentPadding; + UIEdgeInsets textContainerInset = self.textContainerInset; + /// https://github.com/Tencent/QMUI_iOS/issues/1601 + if (@available(iOS 17.0, *)) { + textContainerInset.top = MAX(textContainerInset.top, 0); + textContainerInset.left = MAX(textContainerInset.left, 0); + textContainerInset.bottom = MAX(textContainerInset.bottom, 0); + textContainerInset.right = MAX(textContainerInset.right, 0); + } + return UIEdgeInsetsConcat(UIEdgeInsetsConcat(UIEdgeInsetsConcat(textContainerInset, self.placeholderMargins), UIEdgeInsetsMake(0, padding, 0, padding)), self.adjustedContentInset); } - (void)setFrame:(CGRect)frame { diff --git a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m index d73a5326..8a1b974b 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m +++ b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m @@ -18,6 +18,7 @@ #import "UIViewController+QMUITheme.h" #import "QMUIThemePrivate.h" #import "UITraitCollection+QMUI.h" +#import "UIApplication+QMUI.h" NSString *const QMUIThemeDidChangeNotification = @"QMUIThemeDidChangeNotification"; @@ -137,7 +138,7 @@ - (void)removeTheme:(NSObject *)theme { - (void)notifyThemeChanged { [[NSNotificationCenter defaultCenter] postNotificationName:QMUIThemeDidChangeNotification object:self]; - [UIApplication.sharedApplication.windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { + [UIApplication.sharedApplication.qmui_windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { if (!window.hidden && window.alpha > 0.01 && window.rootViewController) { [window.rootViewController qmui_themeDidChangeByManager:self identifier:self.currentThemeIdentifier theme:self.currentTheme]; diff --git a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m index 558dcae2..2f394996 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m +++ b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemePrivate.m @@ -397,6 +397,19 @@ + (void)load { }; }); + OverrideImplementation([UIView class], @selector(setBackgroundColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIView *selfObject, UIColor *backgroundColor) { + + /// https://github.com/Tencent/QMUI_iOS/issues/1597 + selfObject.layer.qcl_originalBackgroundColor = backgroundColor.qmui_isQMUIDynamicColor ? backgroundColor : nil; + + // call super + void (*originSelectorIMP)(id, SEL, UIColor *); + originSelectorIMP = (void (*)(id, SEL, UIColor *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, backgroundColor); + }; + }); + OverrideImplementation([CALayer class], @selector(setBorderColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^(CALayer *selfObject, CGColorRef color) { diff --git a/QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m b/QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m index 0be2e6df..463905c5 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m +++ b/QMUIKit/QMUIComponents/QMUITheme/UIColor+QMUITheme.m @@ -104,7 +104,7 @@ - (CGColorRef)CGColor { CGColorRef cgColor = CGColorCreate(spaceRef, (CGFloat[]){rawColor.qmui_red, rawColor.qmui_green, rawColor.qmui_blue, rawColor.qmui_alpha}); CGColorSpaceRelease(spaceRef); - [(__bridge id)(cgColor) qmui_bindObject:self forKey:QMUICGColorOriginalColorBindKey]; + [(__bridge id)(cgColor) qmui_bindObjectWeakly:self forKey:QMUICGColorOriginalColorBindKey]; return (CGColorRef)CFAutorelease(cgColor); } diff --git a/QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m b/QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m index 32f91c15..9376d59f 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m +++ b/QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m @@ -139,6 +139,15 @@ - (void)qmui_themeDidChangeByManager:(QMUIThemeManager *)manager identifier:(__k BOOL isValidatedImage = [value isKindOfClass:QMUIThemeImage.class] && (!manager || [((QMUIThemeImage *)value).managerName isEqual:manager.name]); BOOL isValidatedEffect = [value isKindOfClass:QMUIThemeVisualEffect.class] && (!manager || [((QMUIThemeVisualEffect *)value).managerName isEqual:manager.name]); BOOL isOtherObject = ![value isKindOfClass:UIColor.class] && ![value isKindOfClass:UIImage.class] && ![value isKindOfClass:UIVisualEffect.class];// 支持所有非 color、image、effect 的其他对象,例如 NSAttributedString + + // iOS 17,切换主题后图片没有更新 + // https://github.com/Tencent/QMUI_iOS/issues/1507 + if (@available(iOS 17.0, *)) { + if (isValidatedImage) { + value = [(QMUIThemeImage *)value copy]; + } + } + if (isOtherObject || isValidatedColor || isValidatedImage || isValidatedEffect) { [self performSelector:setter withObject:value]; } diff --git a/QMUIKit/QMUIComponents/QMUITips.h b/QMUIKit/QMUIComponents/QMUITips.h index 3aeb0b4e..83db3849 100644 --- a/QMUIKit/QMUIComponents/QMUITips.h +++ b/QMUIKit/QMUIComponents/QMUITips.h @@ -17,10 +17,7 @@ #import "QMUIToastView.h" // 自动计算秒数的标志符,在 delay 里面赋值 QMUITipsAutomaticallyHideToastSeconds 即可通过自动计算 tips 消失的秒数 -extern const NSInteger QMUITipsAutomaticallyHideToastSeconds; - -/// 默认的 parentView -#define DefaultTipsParentView (UIApplication.sharedApplication.delegate.window) +UIKIT_EXTERN const NSInteger QMUITipsAutomaticallyHideToastSeconds; /** * 简单封装了 QMUIToastView,支持弹出纯文本、loading、succeed、error、info 等五种 tips。如果这些接口还满足不了业务的需求,可以通过 QMUITips 的分类自行添加接口。 diff --git a/QMUIKit/QMUIComponents/QMUITips.m b/QMUIKit/QMUIComponents/QMUITips.m index 45aa0099..909ce23f 100644 --- a/QMUIKit/QMUIComponents/QMUITips.m +++ b/QMUIKit/QMUIComponents/QMUITips.m @@ -18,9 +18,13 @@ #import "QMUIToastContentView.h" #import "QMUIToastBackgroundView.h" #import "NSString+QMUI.h" +#import "UIApplication+QMUI.h" const NSInteger QMUITipsAutomaticallyHideToastSeconds = -1; +/// 默认的 parentView +#define QMUIDefaultTipsParentView (UIApplication.sharedApplication.qmui_delegateWindow) + @interface QMUITips () @property(nonatomic, strong) UIView *contentCustomView; @@ -199,11 +203,11 @@ + (QMUITips *)showLoading:(NSString *)text detailText:(NSString *)detailText inV } + (QMUITips *)showWithText:(nullable NSString *)text { - return [self showWithText:text detailText:nil inView:DefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; + return [self showWithText:text detailText:nil inView:QMUIDefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; } + (QMUITips *)showWithText:(nullable NSString *)text detailText:(nullable NSString *)detailText { - return [self showWithText:text detailText:detailText inView:DefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; + return [self showWithText:text detailText:detailText inView:QMUIDefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; } + (QMUITips *)showWithText:(NSString *)text inView:(UIView *)view { @@ -225,11 +229,11 @@ + (QMUITips *)showWithText:(NSString *)text detailText:(NSString *)detailText in } + (QMUITips *)showSucceed:(nullable NSString *)text { - return [self showSucceed:text detailText:nil inView:DefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; + return [self showSucceed:text detailText:nil inView:QMUIDefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; } + (QMUITips *)showSucceed:(nullable NSString *)text detailText:(nullable NSString *)detailText { - return [self showSucceed:text detailText:detailText inView:DefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; + return [self showSucceed:text detailText:detailText inView:QMUIDefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; } + (QMUITips *)showSucceed:(NSString *)text inView:(UIView *)view { @@ -251,11 +255,11 @@ + (QMUITips *)showSucceed:(NSString *)text detailText:(NSString *)detailText inV } + (QMUITips *)showError:(nullable NSString *)text { - return [self showError:text detailText:nil inView:DefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; + return [self showError:text detailText:nil inView:QMUIDefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; } + (QMUITips *)showError:(nullable NSString *)text detailText:(nullable NSString *)detailText { - return [self showError:text detailText:detailText inView:DefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; + return [self showError:text detailText:detailText inView:QMUIDefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; } + (QMUITips *)showError:(NSString *)text inView:(UIView *)view { @@ -277,11 +281,11 @@ + (QMUITips *)showError:(NSString *)text detailText:(NSString *)detailText inVie } + (QMUITips *)showInfo:(nullable NSString *)text { - return [self showInfo:text detailText:nil inView:DefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; + return [self showInfo:text detailText:nil inView:QMUIDefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; } + (QMUITips *)showInfo:(nullable NSString *)text detailText:(nullable NSString *)detailText { - return [self showInfo:text detailText:detailText inView:DefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; + return [self showInfo:text detailText:detailText inView:QMUIDefaultTipsParentView hideAfterDelay:QMUITipsAutomaticallyHideToastSeconds]; } + (QMUITips *)showInfo:(NSString *)text inView:(UIView *)view { diff --git a/QMUIKit/QMUIComponents/QMUIWindowSizeMonitor.m b/QMUIKit/QMUIComponents/QMUIWindowSizeMonitor.m index a926adad..cbef6c45 100644 --- a/QMUIKit/QMUIComponents/QMUIWindowSizeMonitor.m +++ b/QMUIKit/QMUIComponents/QMUIWindowSizeMonitor.m @@ -15,6 +15,7 @@ #import "QMUIWindowSizeMonitor.h" #import "QMUICore.h" #import "NSPointerArray+QMUI.h" +#import "UIApplication+QMUI.h" @interface NSObject (QMUIWindowSizeMonitor_Private) @@ -44,7 +45,7 @@ - (void)qwsm_addSizeObserver:(NSObject *)observer; @implementation NSObject (QMUIWindowSizeMonitor) - (void)qmui_addSizeObserverForMainWindow:(QMUIWindowSizeObserverHandler)handler { - [self qmui_addSizeObserverForWindow:UIApplication.sharedApplication.delegate.window handler:handler]; + [self qmui_addSizeObserverForWindow:UIApplication.sharedApplication.qmui_delegateWindow handler:handler]; } - (void)qmui_addSizeObserverForWindow:(UIWindow *)window handler:(QMUIWindowSizeObserverHandler)handler { diff --git a/QMUIKit/QMUICore/QMUICommonDefines.h b/QMUIKit/QMUICore/QMUICommonDefines.h index 8f77b6d4..17361d95 100644 --- a/QMUIKit/QMUICore/QMUICommonDefines.h +++ b/QMUIKit/QMUICore/QMUICommonDefines.h @@ -81,6 +81,11 @@ #define IOS18_SDK_ALLOWED YES #endif +#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 260000 +/// 当前编译使用的 Base SDK 版本为 iOS 26.0 及以上 +#define IOS26_SDK_ALLOWED YES +#endif + #pragma mark - Clang #define ArgumentToString(macro) #macro diff --git a/QMUIKit/QMUICore/QMUIConfiguration.m b/QMUIKit/QMUICore/QMUIConfiguration.m index ef74deb8..701ba05c 100644 --- a/QMUIKit/QMUICore/QMUIConfiguration.m +++ b/QMUIKit/QMUICore/QMUIConfiguration.m @@ -1029,7 +1029,7 @@ - (void)setDefaultStatusBarStyle:(UIStatusBarStyle)defaultStatusBarStyle { - (NSArray *)appearanceUpdatingViewControllersOfClasses:(NSArray> *)classes { if (!classes.count) return nil; NSMutableArray *viewControllers = [NSMutableArray array]; - [UIApplication.sharedApplication.windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { + [UIApplication.sharedApplication.qmui_windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { if (window.rootViewController) { [viewControllers addObjectsFromArray:[window.rootViewController qmui_existingViewControllersOfClasses:classes]]; } diff --git a/QMUIKit/QMUICore/QMUIHelper.h b/QMUIKit/QMUICore/QMUIHelper.h index 1129501b..46946606 100644 --- a/QMUIKit/QMUICore/QMUIHelper.h +++ b/QMUIKit/QMUICore/QMUIHelper.h @@ -274,6 +274,9 @@ NS_ASSUME_NONNULL_BEGIN */ @property(class, nonatomic, assign, readonly) BOOL canUpdateAppearance; +/// iOS 26 液态玻璃 +@property (class, nonatomic, readonly) BOOL isUsedLiquidGlass; + @end @interface QMUIHelper (Animation) diff --git a/QMUIKit/QMUICore/QMUIHelper.m b/QMUIKit/QMUICore/QMUIHelper.m index c313d23b..5525d690 100644 --- a/QMUIKit/QMUICore/QMUIHelper.m +++ b/QMUIKit/QMUICore/QMUIHelper.m @@ -24,6 +24,9 @@ #import #import #import +#import "UIApplication+QMUI.h" +#import "QMUICommonDefines.h" +#import "UIWindow+QMUI.h" NSString *const kQMUIResourcesBundleName = @"QMUIResources"; @@ -546,7 +549,7 @@ + (BOOL)isNotchedScreen { UIEdgeInsets peripheryInsets = UIEdgeInsetsZero; [[UIScreen mainScreen] qmui_performSelector:peripheryInsetsSelector withPrimitiveReturnValue:&peripheryInsets]; if (peripheryInsets.bottom <= 0) { - UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + UIWindow *window = [UIWindow qmui_windowWithWindowScene:UIApplication.sharedApplication.qmui_delegateWindow.windowScene]; peripheryInsets = window.safeAreaInsets; if (peripheryInsets.bottom <= 0) { // 使用一个强制竖屏的 rootViewController,避免一个仅支持竖屏的 App 在横屏启动时会受这里创建的 window 的影响,导致状态栏、safeAreaInsets 等错乱 @@ -755,7 +758,7 @@ + (CGFloat)preferredLayoutAsSimilarScreenWidthForIPad { @([self screenSizeFor58Inch].width), @([self screenSizeFor40Inch].width)]; preferredLayoutWidth = SCREEN_WIDTH; - UIWindow *window = UIApplication.sharedApplication.delegate.window ?: [[UIWindow alloc] init];// iOS 9 及以上的系统,新 init 出来的 window 自动被设置为当前 App 的宽度 + UIWindow *window = UIApplication.sharedApplication.qmui_delegateWindow ?: [[UIWindow alloc] init];// iOS 9 及以上的系统,新 init 出来的 window 自动被设置为当前 App 的宽度 CGFloat windowWidth = CGRectGetWidth(window.bounds); for (NSInteger i = 0; i < widths.count; i++) { if (windowWidth <= widths[i].qmui_CGFloatValue) { @@ -1059,7 +1062,7 @@ + (CGSize)applicationSize { CGSize applicationSize = CGSizeMake(applicationFrame.size.width + applicationFrame.origin.x, applicationFrame.size.height + applicationFrame.origin.y); if (CGSizeEqualToSize(applicationSize, CGSizeZero)) { // 实测 MacCatalystApp 通过 [UIScreen mainScreen].applicationFrame 拿不到大小,这里做一下保护 - UIWindow *window = UIApplication.sharedApplication.delegate.window; + UIWindow *window = UIApplication.sharedApplication.qmui_delegateWindow; if (window) { applicationSize = window.bounds.size; } else { @@ -1136,13 +1139,13 @@ + (CGFloat)navigationBarMaxYConstant { @implementation QMUIHelper (UIApplication) + (void)dimmedApplicationWindow { - UIWindow *window = UIApplication.sharedApplication.delegate.window; + UIWindow *window = UIApplication.sharedApplication.qmui_delegateWindow; window.tintAdjustmentMode = UIViewTintAdjustmentModeDimmed; [window tintColorDidChange]; } + (void)resetDimmedApplicationWindow { - UIWindow *window = UIApplication.sharedApplication.delegate.window; + UIWindow *window = UIApplication.sharedApplication.qmui_delegateWindow; window.tintAdjustmentMode = UIViewTintAdjustmentModeAutomatic; [window tintColorDidChange]; } @@ -1164,6 +1167,23 @@ + (BOOL)canUpdateAppearance { return YES; } ++ (BOOL)isUsedLiquidGlass { + static BOOL result = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ +#ifdef IOS26_SDK_ALLOWED + if (@available(iOS 26.0, *)) { + result = ![[NSBundle.mainBundle objectForInfoDictionaryKey:@"UIDesignRequiresCompatibility"] boolValue]; + } else { + result = NO; + } +#else + result = NO; +#endif + }); + return result; +} + @end @implementation QMUIHelper (Animation) diff --git a/QMUIKit/UIKitExtensions/UIApplication+QMUI.h b/QMUIKit/UIKitExtensions/UIApplication+QMUI.h index 94250659..fd3df985 100644 --- a/QMUIKit/UIKitExtensions/UIApplication+QMUI.h +++ b/QMUIKit/UIKitExtensions/UIApplication+QMUI.h @@ -20,6 +20,12 @@ NS_ASSUME_NONNULL_BEGIN /// 判断当前的 App 是否已经完全启动 @property(nonatomic, assign, readonly) BOOL qmui_didFinishLaunching; + +@property (nonatomic, readonly) NSArray<__kindof UIWindow *> *qmui_windows; + +@property (nullable, nonatomic, readonly) __kindof UIWindow *qmui_keyWindow; +@property (nullable, nonatomic, readonly) __kindof UIWindow *qmui_delegateWindow; + @end NS_ASSUME_NONNULL_END diff --git a/QMUIKit/UIKitExtensions/UIApplication+QMUI.m b/QMUIKit/UIKitExtensions/UIApplication+QMUI.m index 36129ad3..e3be8edd 100644 --- a/QMUIKit/UIKitExtensions/UIApplication+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIApplication+QMUI.m @@ -45,4 +45,54 @@ - (void)qmui_handleDidFinishLaunchingNotification:(NSNotification *)notification [NSNotificationCenter.defaultCenter removeObserver:self name:UIApplicationDidFinishLaunchingNotification object:nil]; } +- (NSArray<__kindof UIWindow *> *)qmui_windows { + __block NSArray *windows = nil; + if (@available(iOS 13.0, *)) { + [self.connectedScenes enumerateObjectsUsingBlock:^(UIScene *scene, BOOL *stop) { + if ([scene isKindOfClass:UIWindowScene.class] && [scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication]) { + windows = [(UIWindowScene *)scene windows]; + *stop = YES; + } + }]; + } + if (!windows || windows.count == 0) { + windows = self.windows; + } + return windows ? : @[]; +} + +- (nullable __kindof UIWindow *)qmui_keyWindow { + __block UIWindow *keyWindow = nil; + [self.qmui_windows enumerateObjectsUsingBlock:^(__kindof UIWindow *window, NSUInteger idx, BOOL *stop) { + if (window.isKeyWindow && !window.isHidden) { + keyWindow = window; + *stop = YES; + } + }]; + if (!keyWindow) { + keyWindow = self.qmui_delegateWindow; + } + return keyWindow; +} + +- (nullable __kindof UIWindow *)qmui_delegateWindow { + __block UIWindow *delegateWindow = nil; + if (@available(iOS 13.0, *)) { + [self.connectedScenes enumerateObjectsUsingBlock:^(UIScene *scene, BOOL *stop) { + if ([scene isKindOfClass:UIWindowScene.class] && [scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication]) { + NSAssert(!scene.delegate || [scene.delegate respondsToSelector:@selector(window)], @"请检查是否有window属性"); + if ([scene.delegate respondsToSelector:@selector(window)]) { + delegateWindow = [scene.delegate performSelector:@selector(window)]; + *stop = YES; + } + } + }]; + } + NSAssert(delegateWindow || !self.delegate || [self.delegate respondsToSelector:@selector(window)], @"请检查是否有window属性"); + if (!delegateWindow && [self.delegate respondsToSelector:@selector(window)]) { + delegateWindow = [self.delegate performSelector:@selector(window)]; + } + return delegateWindow; +} + @end diff --git a/QMUIKit/UIKitExtensions/UIBarItem+QMUI.m b/QMUIKit/UIKitExtensions/UIBarItem+QMUI.m index 4812e800..465409e4 100644 --- a/QMUIKit/UIKitExtensions/UIBarItem+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIBarItem+QMUI.m @@ -35,9 +35,27 @@ + (void)load { }); // -[UITabBarItem setView:] - ExtendImplementationOfVoidMethodWithSingleArgument([UITabBarItem class], @selector(setView:), UIView *, ^(UITabBarItem *selfObject, UIView *firstArgv) { - [UIBarItem setView:firstArgv inBarItem:selfObject]; - }); + if (@available(iOS 18.0, *)) { + OverrideImplementation([UITabBar class], @selector(setItems:animated:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UITabBar *selfObject, NSArray *items, BOOL animated) { + + // call super + void (*originSelectorIMP)(id, SEL, NSArray *, BOOL); + originSelectorIMP = (void (*)(id, SEL, NSArray *, BOOL))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, items, animated); + + [items enumerateObjectsUsingBlock:^(UITabBarItem *item, NSUInteger idx, BOOL *stop) { + if (item.qmui_view) { + [UIBarItem setView:item.qmui_view inBarItem:item]; + } + }]; + }; + }); + } else { + ExtendImplementationOfVoidMethodWithSingleArgument([UITabBarItem class], @selector(setView:), UIView *, ^(UITabBarItem *selfObject, UIView *firstArgv) { + [UIBarItem setView:firstArgv inBarItem:selfObject]; + }); + } }); } diff --git a/QMUIKit/UIKitExtensions/UIColor+QMUI.m b/QMUIKit/UIKitExtensions/UIColor+QMUI.m index af1140cd..53b3dbd6 100644 --- a/QMUIKit/UIKitExtensions/UIColor+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIColor+QMUI.m @@ -341,7 +341,7 @@ + (void)load { result = CGColorCreate(spaceRef, (CGFloat[]){color.qmui_red, color.qmui_green, color.qmui_blue, color.qmui_alpha}); CGColorSpaceRelease(spaceRef); - [(__bridge id)(result) qmui_bindObject:selfObject forKey:QMUICGColorOriginalColorBindKey]; + [(__bridge id)(result) qmui_bindObjectWeakly:selfObject forKey:QMUICGColorOriginalColorBindKey]; return (CGColorRef)CFAutorelease(result); } diff --git a/QMUIKit/UIKitExtensions/UIInterface+QMUI.m b/QMUIKit/UIKitExtensions/UIInterface+QMUI.m index 18b01795..c18f36d2 100644 --- a/QMUIKit/UIKitExtensions/UIInterface+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIInterface+QMUI.m @@ -15,6 +15,7 @@ #import "UIInterface+QMUI.h" #import "QMUICore.h" +#import "UIApplication+QMUI.h" @implementation QMUIHelper (QMUI_Interface) @@ -189,7 +190,7 @@ - (BOOL)qmui_rotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrien __block BOOL result = YES; UIInterfaceOrientationMask mask = 1 << interfaceOrientation; - UIWindow *window = self.view.window ?: UIApplication.sharedApplication.delegate.window; + UIWindow *window = self.view.window ?: UIApplication.sharedApplication.qmui_delegateWindow; [window.windowScene requestGeometryUpdateWithPreferences:[[UIWindowSceneGeometryPreferencesIOS alloc] initWithInterfaceOrientations:mask] errorHandler:^(NSError * _Nonnull error) { if (error) { result = NO; diff --git a/QMUIKit/UIKitExtensions/UIMenuController+QMUI.m b/QMUIKit/UIKitExtensions/UIMenuController+QMUI.m index 572dba84..5f2dac86 100644 --- a/QMUIKit/UIKitExtensions/UIMenuController+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIMenuController+QMUI.m @@ -15,6 +15,7 @@ #import "UIMenuController+QMUI.h" #import "QMUICore.h" #import "NSArray+QMUI.h" +#import "UIApplication+QMUI.h" @implementation UIMenuController (QMUI) @@ -110,7 +111,7 @@ + (UIWindow *)qmuimc_menuControllerWindow { if (kMenuControllerWindow && !kMenuControllerWindow.hidden) { return kMenuControllerWindow; } - [UIApplication.sharedApplication.windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { + [UIApplication.sharedApplication.qmui_windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { NSString *windowString = [NSString stringWithFormat:@"UI%@%@", @"Text", @"EffectsWindow"]; if ([window isKindOfClass:NSClassFromString(windowString)] && !window.hidden) { if (@available(iOS 16.0, *)) { @@ -136,8 +137,8 @@ + (UIWindow *)qmuimc_menuControllerWindow { + (UIWindow *)qmuimc_firstResponderWindowExceptMainWindow { __block UIWindow *resultWindow = nil; - [UIApplication.sharedApplication.windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { - if (window != UIApplication.sharedApplication.delegate.window) { + [UIApplication.sharedApplication.qmui_windows enumerateObjectsUsingBlock:^(__kindof UIWindow * _Nonnull window, NSUInteger idx, BOOL * _Nonnull stop) { + if (window != UIApplication.sharedApplication.qmui_delegateWindow) { UIResponder *responder = [UIMenuController qmuimc_findFirstResponderInView:window]; if (responder) { resultWindow = window; diff --git a/QMUIKit/UIKitExtensions/UINavigationBar+QMUI.m b/QMUIKit/UIKitExtensions/UINavigationBar+QMUI.m index 8d82a777..94dc1f12 100644 --- a/QMUIKit/UIKitExtensions/UINavigationBar+QMUI.m +++ b/QMUIKit/UIKitExtensions/UINavigationBar+QMUI.m @@ -328,7 +328,7 @@ + (void)load { } }; }); - +#ifdef DEBUG // 尚未应用 UIAppearance 就已经修改 bar 的样式的场景,可能导致 bar 样式无法与全局保持一致,所以这里做个提醒 // https://github.com/Tencent/QMUI_iOS/issues/1451 // - [UINavigationBar setStandardAppearance:] @@ -339,27 +339,41 @@ + (void)load { void (*originSelectorIMP)(id, SEL, UINavigationBarAppearance *); originSelectorIMP = (void (*)(id, SEL, UINavigationBarAppearance *))originalIMPProvider(); originSelectorIMP(selfObject, originCMD, firstArgv); - + // 这里只希望识别 UINavigationController 自带的 navigationBar,不希望处理业务自己 new 的 bar,所以用 superview 是否为 UILayoutContainerView 来作为判断条件。 - BOOL isSystemBar = [NSStringFromClass(selfObject.superview.class) hasPrefix:@"UILayoutContainer"]; + BOOL isSystemBar = [NSStringFromClass(selfObject.superview.class) hasPrefix:@"UILayoutContainer"] && [selfObject.superview.qmui_viewController isKindOfClass:UINavigationController.class]; BOOL alreadyMoveToWindow = !!selfObject.window; BOOL isPresenting = NO; if (!alreadyMoveToWindow) { UINavigationController *nav = [selfObject.qmui_viewController isKindOfClass:UINavigationController.class] ? selfObject.qmui_viewController : nil; - isPresenting = nav && nav.presentedViewController; + isPresenting = nav && nav.presentedViewController; } if (isSystemBar && !alreadyMoveToWindow && !isPresenting) { QMUIAssert(NO, @"UINavigationBar (QMUI)", @"试图在 UINavigationBar 尚未添加到 window 上时就修改它的样式,可能导致 UINavigationBar 的样式无法与全局保持一致。"); } }; }); +#endif } #endif }); } - (UIView *)qmui_contentView { - return [self valueForKeyPath:@"visualProvider.contentView"]; + if (QMUIHelper.isUsedLiquidGlass) { + for (UIView *subview in self.subviews) { + static NSString *clsString = nil; + if (!clsString) { + clsString = [NSString stringWithFormat:@"%@.%@%@", @"UIKit", @"NavigationBar", @"ContentView"]; + } + if ([subview isKindOfClass:NSClassFromString(clsString)]) { + return subview; + } + } + return nil; + } else { + return [self valueForKeyPath:@"visualProvider.contentView"]; + } } - (void)qmuinb_fixTitleViewLayoutInIOS16 { diff --git a/QMUIKit/UIKitExtensions/UISearchBar+QMUI.m b/QMUIKit/UIKitExtensions/UISearchBar+QMUI.m index 4e50a0b5..503a57cb 100644 --- a/QMUIKit/UIKitExtensions/UISearchBar+QMUI.m +++ b/QMUIKit/UIKitExtensions/UISearchBar+QMUI.m @@ -40,46 +40,86 @@ + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - void (^setupCancelButtonBlock)(UISearchBar *, UIButton *) = ^void(UISearchBar *searchBar, UIButton *cancelButton) { - if (searchBar.qmui_alwaysEnableCancelButton && !searchBar.qmui_searchController) { - cancelButton.enabled = YES; - } - - if (cancelButton && searchBar.qmui_cancelButtonFont) { - cancelButton.titleLabel.font = searchBar.qmui_cancelButtonFont; - } - - if (cancelButton && !cancelButton.qmui_frameWillChangeBlock) { - __weak __typeof(searchBar)weakSearchBar = searchBar; - cancelButton.qmui_frameWillChangeBlock = ^CGRect(UIButton *aCancelButton, CGRect followingFrame) { - return [weakSearchBar qmuisb_adjustCancelButtonFrame:followingFrame]; + if (QMUIHelper.isUsedLiquidGlass) { + ExtendImplementationOfVoidMethodWithoutArguments(NSClassFromString(@"_UISearchBarVisualProviderIOS"), NSSelectorFromString(@"setUpSearchField"), ^(NSObject *selfObject) { + UISearchBar *searchBar = [selfObject qmui_valueForKey:@"_searchBar"]; + QMUIAssert([searchBar isKindOfClass:UISearchBar.class], @"UISearchBar (QMUI)", @"Can not find UISearchBar"); + if (![searchBar isKindOfClass:UISearchBar.class]) { + return; + } + if (searchBar.qmui_alwaysEnableCancelButton && !searchBar.qmui_searchController) { + BOOL alwaysEnableCancelButton = YES; + [selfObject qmui_performSelector:NSSelectorFromString(@"setShowsClearButtonWhenEmpty:") withArguments:&alwaysEnableCancelButton, nil]; + } + }); + OverrideImplementation([UISearchTextField class], @selector(addSubview:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UISearchTextField *selfObject, UIView *subview) { + + // call super + void (*originSelectorIMP)(id, SEL, UIView *); + originSelectorIMP = (void (*)(id, SEL, UIView *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, subview); + + if (![subview isKindOfClass:NSClassFromString(@"_UITextFieldClearButton")]) { + return; + } + UISearchBar *searchBar = (UISearchBar *)selfObject.superview.superview.superview; + QMUIAssert([searchBar isKindOfClass:UISearchBar.class], @"UISearchBar (QMUI)", @"Can not find UISearchBar from cancelButton"); + if (![searchBar isKindOfClass:UISearchBar.class]) { + return; + } + UIButton *cancelButton = [searchBar qmui_cancelButton]; + if (cancelButton && searchBar.qmui_cancelButtonFont) { + cancelButton.titleLabel.font = searchBar.qmui_cancelButtonFont; + } + if (cancelButton && !cancelButton.qmui_frameWillChangeBlock) { + __weak __typeof(searchBar)weakSearchBar = searchBar; + cancelButton.qmui_frameWillChangeBlock = ^CGRect(UIButton *aCancelButton, CGRect followingFrame) { + return [weakSearchBar qmuisb_adjustCancelButtonFrame:followingFrame]; + }; + } }; - } - }; - - // iOS 13 开始 UISearchBar 内部的输入框、取消按钮等 subviews 都由这个 class 创建、管理 - ExtendImplementationOfVoidMethodWithoutArguments(NSClassFromString(@"_UISearchBarVisualProviderIOS"), NSSelectorFromString(@"setUpCancelButton"), ^(NSObject *selfObject) { - UIButton *cancelButton = [selfObject qmui_valueForKey:@"cancelButton"]; - UISearchBar *searchBar = (UISearchBar *)cancelButton.superview.superview.superview; - QMUIAssert([searchBar isKindOfClass:UISearchBar.class], @"UISearchBar (QMUI)", @"Can not find UISearchBar from cancelButton"); - setupCancelButtonBlock(searchBar, cancelButton); - }); - - OverrideImplementation(NSClassFromString(@"UINavigationButton"), @selector(setEnabled:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIButton *selfObject, BOOL firstArgv) { + }); + } else { + // iOS 13 开始 UISearchBar 内部的输入框、取消按钮等 subviews 都由这个 class 创建、管理 + ExtendImplementationOfVoidMethodWithoutArguments(NSClassFromString(@"_UISearchBarVisualProviderIOS"), NSSelectorFromString(@"setUpCancelButton"), ^(NSObject *selfObject) { + UIButton *cancelButton = [selfObject qmui_valueForKey:@"cancelButton"]; + UISearchBar *searchBar = (UISearchBar *)cancelButton.superview.superview.superview; + QMUIAssert([searchBar isKindOfClass:UISearchBar.class], @"UISearchBar (QMUI)", @"Can not find UISearchBar from cancelButton"); + if (![searchBar isKindOfClass:UISearchBar.class]) { + return; + } + if (searchBar.qmui_alwaysEnableCancelButton && !searchBar.qmui_searchController) { + cancelButton.enabled = YES; + } - UISearchBar *searchBar = (UISearchBar *)selfObject.superview.superview.superview;; - if ([searchBar isKindOfClass:UISearchBar.class] && searchBar.qmui_alwaysEnableCancelButton && !searchBar.qmui_searchController) { - firstArgv = YES; + if (cancelButton && searchBar.qmui_cancelButtonFont) { + cancelButton.titleLabel.font = searchBar.qmui_cancelButtonFont; } - // call super - void (*originSelectorIMP)(id, SEL, BOOL); - originSelectorIMP = (void (*)(id, SEL, BOOL))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, firstArgv); - }; - }); - + if (cancelButton && !cancelButton.qmui_frameWillChangeBlock) { + __weak __typeof(searchBar)weakSearchBar = searchBar; + cancelButton.qmui_frameWillChangeBlock = ^CGRect(UIButton *aCancelButton, CGRect followingFrame) { + return [weakSearchBar qmuisb_adjustCancelButtonFrame:followingFrame]; + }; + } + }); + OverrideImplementation(NSClassFromString(@"UINavigationButton"), @selector(setEnabled:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIButton *selfObject, BOOL firstArgv) { + + UISearchBar *searchBar = (UISearchBar *)selfObject.superview.superview.superview;; + if ([searchBar isKindOfClass:UISearchBar.class] && searchBar.qmui_alwaysEnableCancelButton && !searchBar.qmui_searchController) { + firstArgv = YES; + } + + // call super + void (*originSelectorIMP)(id, SEL, BOOL); + originSelectorIMP = (void (*)(id, SEL, BOOL))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, firstArgv); + }; + }); + } + ExtendImplementationOfVoidMethodWithSingleArgument([UISearchBar class], @selector(setPlaceholder:), NSString *, (^(UISearchBar *selfObject, NSString *placeholder) { if (selfObject.qmui_placeholderColor || selfObject.qmui_font) { NSMutableAttributedString *string = selfObject.searchTextField.attributedPlaceholder.mutableCopy; @@ -334,8 +374,12 @@ - (UIFont *)qmui_font { } - (UIButton *)qmui_cancelButton { - UIButton *cancelButton = [self qmui_valueForKey:@"cancelButton"]; - return cancelButton; + if (QMUIHelper.isUsedLiquidGlass) { + return [self.searchTextField qmui_valueForKey:@"_clearButton"]; + } else { + UIButton *cancelButton = [self qmui_valueForKey:@"cancelButton"]; + return cancelButton; + } } static char kAssociatedObjectKey_cancelButtonFont; diff --git a/QMUIKit/UIKitExtensions/UISlider+QMUI.m b/QMUIKit/UIKitExtensions/UISlider+QMUI.m index 70747401..6278f2ab 100644 --- a/QMUIKit/UIKitExtensions/UISlider+QMUI.m +++ b/QMUIKit/UIKitExtensions/UISlider+QMUI.m @@ -43,8 +43,21 @@ - (UIView *)qmui_thumbView { } if (!slider) return nil; - UIView *thumbView = [slider qmui_valueForKey:@"thumbView"] ?: [slider qmui_valueForKey:@"innerThumbView"]; - return thumbView; + if (QMUIHelper.isUsedLiquidGlass) { + for (UIView *subview in slider.subviews) { + if ([subview isKindOfClass:NSClassFromString(@"_UILiquidLensView")]) { + for (UIImageView *imageView in subview.subviews) { + if ([imageView isKindOfClass:UIImageView.class]) { + return imageView; + } + } + } + } + return nil; + } else { + UIView *thumbView = [slider qmui_valueForKey:@"thumbView"] ?: [slider qmui_valueForKey:@"innerThumbView"]; + return thumbView; + } } static char kAssociatedObjectKey_trackHeight; @@ -243,6 +256,11 @@ - (void)qmuisl_handleValueChanged:(UISlider *)slider { NSUInteger step = [slider qmuisl_stepWithValue:slider.value]; if (step != slider.qmuisl_precedingStep) { + [self.qmuisl_stepControls enumerateObjectsUsingBlock:^(QMUISliderStepControl * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + obj.userInteractionEnabled = idx != step;// 让 stepControl 不要影响 thumbView 的事件 + obj.indicator.hidden = idx == step; + }]; + if (slider.qmui_stepDidChangeBlock) { slider.qmui_stepDidChangeBlock(slider, slider.qmuisl_precedingStep); } @@ -374,13 +392,7 @@ - (void)qmuisl_layoutStepControls { if (!count) return; // 根据当前 thumbView 的位置,控制重叠的那个 stepControl 的事件响应和显隐,由于 slider 可能是 continuous 的,所以这段逻辑必须每次 layout 都调用,不能放在 layoutCachedKey 的保护里 - CGRect thumbRect = self.qmui_thumbView.frame; CGRect trackRect = [self trackRectForBounds:self.bounds]; - NSUInteger step = round((CGRectGetMidX(thumbRect) - CGRectGetMinX(trackRect)) / CGRectGetWidth(trackRect) * (count - 1)); - [self.qmuisl_stepControls enumerateObjectsUsingBlock:^(QMUISliderStepControl * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - obj.userInteractionEnabled = idx != step;// 让 stepControl 不要影响 thumbView 的事件 - obj.indicator.hidden = idx == step; - }]; NSString *layoutCachedKey = [NSString stringWithFormat:@"%.0f-%@", CGRectGetWidth(trackRect), @(count)]; if ([self.qmuisl_layoutCachedKey isEqualToString:layoutCachedKey]) return; diff --git a/QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m b/QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m index c3cd55dd..09809bfe 100644 --- a/QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITabBarItem+QMUI.m @@ -30,7 +30,16 @@ + (UIImageView *)qmui_imageViewInTabBarButton:(UIView *)tabBarButton { if (!tabBarButton) { return nil; } - return [tabBarButton qmui_valueForKey:@"_imageView"]; + if (QMUIHelper.isUsedLiquidGlass) { + for (UIView *subview in tabBarButton.subviews) { + if ([subview isKindOfClass:UIImageView.class]) { + return (UIImageView *)subview; + } + } + return nil; + } else { + return [tabBarButton qmui_valueForKey:@"_imageView"]; + } } @end diff --git a/QMUIKit/UIKitExtensions/UITableView+QMUI.m b/QMUIKit/UIKitExtensions/UITableView+QMUI.m index 7850986a..d13d79a9 100644 --- a/QMUIKit/UIKitExtensions/UITableView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITableView+QMUI.m @@ -21,6 +21,7 @@ #import "QMUILog.h" #import "NSObject+QMUI.h" #import "CALayer+QMUI.h" +#import "QMUICommonDefines.h" const NSUInteger kFloatValuePrecision = 4;// 统一一个小数点运算精度 @@ -238,6 +239,15 @@ - (void)qmui_styledAsQMUITableView { self.qmui_insetGroupedCornerRadius = TableViewInsetGroupedCornerRadius; self.qmui_insetGroupedHorizontalInset = TableViewInsetGroupedHorizontalInset; + +#ifdef IOS26_SDK_ALLOWED + if (@available(iOS 26.0, *)) { + self.topEdgeEffect.hidden = YES; + self.leftEdgeEffect.hidden = YES; + self.bottomEdgeEffect.hidden = YES; + self.rightEdgeEffect.hidden = YES; + } +#endif } - (void)_qmui_configEstimatedRowHeight { diff --git a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m index 843ea3de..c49ea9ba 100644 --- a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m @@ -14,6 +14,7 @@ #import "UITraitCollection+QMUI.h" #import "QMUICore.h" +#import "UIApplication+QMUI.h" @implementation UITraitCollection (QMUI) @@ -63,53 +64,28 @@ + (void)_qmui_overrideTraitCollectionMethodIfNeeded { static UIUserInterfaceStyle qmui_lastNotifiedUserInterfaceStyle; qmui_lastNotifiedUserInterfaceStyle = [UITraitCollection currentTraitCollection].userInterfaceStyle; - // - (void) _willTransitionToTraitCollection:(id)arg1 withTransitionCoordinator:(id)arg2; (0x7fff24711d49) - OverrideImplementation([UIWindow class], NSSelectorFromString([NSString qmui_stringByConcat:@"_", @"willTransitionToTraitCollection:", @"withTransitionCoordinator:", nil]), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIWindow *selfObject, UITraitCollection *traitCollection, id coordinator) { + static NSArray *keyboardWindows; + keyboardWindows = @[@"UIRemoteKeyboardWindow", @"UITextEffectsWindow"]; + + /// https://github.com/Tencent/QMUI_iOS/issues/1634 + OverrideImplementation([UIViewController class], @selector(willTransitionToTraitCollection:withTransitionCoordinator:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIViewController *selfObject, UITraitCollection *newCollection, id coordinator) { // call super - void (*originSelectorIMP)(id, SEL, UITraitCollection *, id ); - originSelectorIMP = (void (*)(id, SEL, UITraitCollection *, id ))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, traitCollection, coordinator); + void (*originSelectorIMP)(id, SEL, UITraitCollection *, id); + originSelectorIMP = (void (*)(id, SEL, UITraitCollection *, id))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, newCollection, coordinator); - BOOL snapshotFinishedOnBackground = traitCollection.userInterfaceLevel == UIUserInterfaceLevelElevated && UIApplication.sharedApplication.applicationState == UIApplicationStateBackground; - // 进入后台且完成截图了就不继续去响应 style 变化(实测 iOS 13.0 iPad 进入后台并完成截图后,仍会多次改变 style,但是系统并没有调用界面的相关刷新方法) - if (selfObject.windowScene && !snapshotFinishedOnBackground) { - UIWindow *firstValidatedWindow = nil; - - if ([NSStringFromClass(selfObject.class) containsString:@"_UIWindowSceneUserInterfaceStyle"]) { // _UIWindowSceneUserInterfaceStyleAnimationSnapshotWindow - firstValidatedWindow = selfObject; - } else { - // 系统会按照这个数组的顺序去更新 window 的 traitCollection,找出最先响应样式更新的 window - NSPointerArray *windows = [[selfObject windowScene] valueForKeyPath:@"_contextBinder._attachedBindables"]; - for (NSUInteger i = 0, count = windows.count; i < count; i++) { - UIWindow *window = [windows pointerAtIndex:i]; - // 例如用 UIWindow 方式显示的弹窗,在消失后,在 windows 数组里会残留一个 nil 的位置,这里过滤掉,否则会导致 App 从桌面唤醒时无法立即显示正确的 style - if (!window) { - continue;; - } - - // 由于 Keyboard 可以通过 keyboardAppearance 来控制 userInterfaceStyle 的 Dark/Light,不一定和系统一样,这里要过滤掉 - if ([window isKindOfClass:NSClassFromString(@"UIRemoteKeyboardWindow")] || [window isKindOfClass:NSClassFromString(@"UITextEffectsWindow")]) { - continue; - } - if (window.overrideUserInterfaceStyle != UIUserInterfaceStyleUnspecified) { - // 这里需要获取到和系统样式同步的 UserInterfaceStyle(所以指定 overrideUserInterfaceStyle 需要跳过) - // 所以当全部 window.overrideUserInterfaceStyle 都指定为非 UIUserInterfaceStyleUnspecified 时将无法获得当前系统的外观 - continue; - } - firstValidatedWindow = window; - break; - } - } - - if (selfObject == firstValidatedWindow) { - if (qmui_lastNotifiedUserInterfaceStyle != traitCollection.userInterfaceStyle) { - qmui_lastNotifiedUserInterfaceStyle = traitCollection.userInterfaceStyle; - [self _qmui_notifyUserInterfaceStyleWillChangeEvents:traitCollection]; - } - } + if (qmui_lastNotifiedUserInterfaceStyle == newCollection.userInterfaceStyle) { + return; + } + UIWindow *window = selfObject.viewIfLoaded.window; + if (!window || [keyboardWindows containsObject:NSStringFromClass(window.class)] || window != UIApplication.sharedApplication.qmui_delegateWindow) { + return; } + qmui_lastNotifiedUserInterfaceStyle = newCollection.userInterfaceStyle; + + [self _qmui_notifyUserInterfaceStyleWillChangeEvents:newCollection]; }; }); } oncePerIdentifier:@"UITraitCollection addUserInterfaceStyleWillChangeObserver"]; diff --git a/QMUIKit/UIKitExtensions/UIView+QMUI.m b/QMUIKit/UIKitExtensions/UIView+QMUI.m index faf17139..e182e4f3 100644 --- a/QMUIKit/UIKitExtensions/UIView+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIView+QMUI.m @@ -45,17 +45,19 @@ + (void)load { }; }); +#ifdef DEBUG // 这个私有方法在 view 被调用 becomeFirstResponder 并且处于 window 上时,才会被调用,所以比 becomeFirstResponder 更适合用来检测 ExtendImplementationOfVoidMethodWithSingleArgument([UIView class], NSSelectorFromString(@"_didChangeToFirstResponder:"), id, ^(UIView *selfObject, id firstArgv) { - if (selfObject == firstArgv && [selfObject conformsToProtocol:@protocol(UITextInput)]) { + if (IS_DEBUG && selfObject == firstArgv && [selfObject conformsToProtocol:@protocol(UITextInput)]) { // 像 QMUIModalPresentationViewController 那种以 window 的形式展示浮层,浮层里的输入框 becomeFirstResponder 的场景,[window makeKeyAndVisible] 被调用后,就会立即走到这里,但此时该 window 尚不是 keyWindow,所以这里延迟到下一个 runloop 里再去判断 dispatch_async(dispatch_get_main_queue(), ^{ - if (IS_DEBUG && ![selfObject isKindOfClass:[UIWindow class]] && selfObject.window && !selfObject.window.keyWindow) { + if (![selfObject isKindOfClass:[UIWindow class]] && selfObject.window && !selfObject.window.isKeyWindow) { [selfObject QMUISymbolicUIViewBecomeFirstResponderWithoutKeyWindow]; } }); } }); +#endif }); } @@ -310,11 +312,16 @@ - (void)setQmui_viewController:(__kindof UIViewController * _Nullable)qmui_viewC - (__kindof UIViewController *)qmui_viewController { if (self.qmui_isControllerRootView) { - return (__kindof UIViewController *)((QMUIWeakObjectContainer *)objc_getAssociatedObject(self, &kAssociatedObjectKey_viewController)).object; + return self.__qmui_associatedViewController; } return self.superview.qmui_viewController; } +- (nullable __kindof UIViewController *)__qmui_associatedViewController { + QMUIWeakObjectContainer *weakContainer = objc_getAssociatedObject(self, &kAssociatedObjectKey_viewController); + return weakContainer.object; +} + @end @interface UIViewController (QMUI_View) @@ -326,8 +333,15 @@ @implementation UIViewController (QMUI_View) + (void)load { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + ExtendImplementationOfVoidMethodWithSingleArgument([UIViewController class], @selector(setView:), UIView *, ^(UIViewController *selfObject, UIView *view) { + if (view.__qmui_associatedViewController != selfObject) { + view.qmui_viewController = selfObject; + } + }); ExtendImplementationOfVoidMethodWithoutArguments([UIViewController class], @selector(viewDidLoad), ^(UIViewController *selfObject) { - selfObject.view.qmui_viewController = selfObject; + if (selfObject.view.__qmui_associatedViewController != selfObject) { + selfObject.view.qmui_viewController = selfObject; + } }); }); } diff --git a/QMUIKit/UIKitExtensions/UIViewController+QMUI.m b/QMUIKit/UIKitExtensions/UIViewController+QMUI.m index de9fbbcf..2b204356 100644 --- a/QMUIKit/UIKitExtensions/UIViewController+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIViewController+QMUI.m @@ -20,6 +20,7 @@ #import "NSObject+QMUI.h" #import "QMUILog.h" #import "UIView+QMUI.h" +#import "UIApplication+QMUI.h" NSNotificationName const QMUIAppSizeWillChangeNotification = @"QMUIAppSizeWillChangeNotification"; NSString *const QMUIPrecedingAppSizeUserInfoKey = @"QMUIPrecedingAppSizeUserInfoKey"; @@ -62,7 +63,7 @@ + (void)load { OverrideImplementation([UIViewController class], @selector(viewWillTransitionToSize:withTransitionCoordinator:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { return ^(UIViewController *selfObject, CGSize size, id coordinator) { - if (selfObject == UIApplication.sharedApplication.delegate.window.rootViewController) { + if (selfObject == UIApplication.sharedApplication.qmui_delegateWindow.rootViewController) { CGSize originalSize = selfObject.view.frame.size; BOOL sizeChanged = !CGSizeEqualToSize(originalSize, size); if (sizeChanged) { @@ -587,7 +588,7 @@ - (void)qmui_animateAlongsideTransition:(void (^ __nullable)(id +NS_ASSUME_NONNULL_BEGIN + @interface UIWindow (QMUI) /** @@ -37,5 +39,10 @@ /// 当前 window 因各种原因(例如其他 window 显式调用 makeKey、当前 keyWindow 被隐藏导致系统自动流转 keyWindow、主动向自身调用 resignKeyWindow 等)导致从 keyWindow 转变为非 keyWindow 时会询问这个 block,业务可在这个 block 里干预当前的流转。 /// 实际场景例如,背后 window 正在显示一个带输入框的 webView 网页,输入框聚焦以升起键盘,此时你再新开一个更高 windowLevel 的 window,盖在 webView 上并且 makeKey,就会发现你的 window 依然被键盘挡住,因为 webView 有个特性是如果有输入框聚焦,则 webView 内部会不断地尝试将输入框 becomeFirstResponder 并且让输入框所在的 window makeKey,这就会抢占了我们刚刚手动盖上来的 window 的 key,所以此时就可以给新开的 window 使用本 block,返回 NO,使 webView 无法抢占 keyWindow,从而避免键盘遮挡。 -@property(nonatomic, copy) BOOL (^qmui_canResignKeyWindowBlock)(UIWindow *selfObject, UIWindow *windowWillBecomeKey); +@property(nonatomic, copy, nullable) BOOL (^qmui_canResignKeyWindowBlock)(UIWindow *selfObject, UIWindow *windowWillBecomeKey); + ++ (instancetype)qmui_windowWithWindowScene:(nullable UIWindowScene *)windowScene; + @end + +NS_ASSUME_NONNULL_END diff --git a/QMUIKit/UIKitExtensions/UIWindow+QMUI.m b/QMUIKit/UIKitExtensions/UIWindow+QMUI.m index 57e03b66..a389ba5a 100644 --- a/QMUIKit/UIKitExtensions/UIWindow+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIWindow+QMUI.m @@ -15,6 +15,7 @@ #import "UIWindow+QMUI.h" #import "QMUICore.h" +#import "UIApplication+QMUI.h" @implementation UIWindow (QMUI) @@ -99,7 +100,7 @@ - (void)qmuiw_hookIfNeeded { } BeginIgnoreDeprecatedWarning - UIWindow *keyWindow = UIApplication.sharedApplication.keyWindow; + UIWindow *keyWindow = UIApplication.sharedApplication.qmui_keyWindow; if (result && keyWindow && keyWindow != selfObject && keyWindow.qmui_canResignKeyWindowBlock) { result = keyWindow.qmui_canResignKeyWindowBlock(keyWindow, selfObject); } @@ -128,4 +129,12 @@ - (void)qmuiw_hookIfNeeded { } oncePerIdentifier:@"UIWindow (QMUI) keyWindow"]; } ++ (instancetype)qmui_windowWithWindowScene:(nullable UIWindowScene *)windowScene { + if (windowScene != nil) { + return [[self.class alloc] initWithWindowScene:windowScene]; + } else { + return [[self.class alloc] initWithFrame:UIScreen.mainScreen.bounds]; + } +} + @end From fba7a8b5f65b54603e3421f54d6f1849c39371f9 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 12 Sep 2025 12:11:44 +0800 Subject: [PATCH 02/22] =?UTF-8?q?UIBarItem=20(QMUIBadge)=EF=BC=8C=E9=80=82?= =?UTF-8?q?=E9=85=8DLiquidGlass?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit qmui_updatesIndicatorView、qmui_badgeView需要改成block实现了,返回一个View实例 --- .../QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m b/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m index 38a93d8f..91629e83 100644 --- a/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m +++ b/QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m @@ -162,7 +162,8 @@ - (CGPoint)qmui_badgeOffsetLandscape { - (void)setQmui_badgeView:(__kindof UIView *)qmui_badgeView { self.qmui_view.qmui_badgeView = qmui_badgeView; - self.qmui_selectedView.qmui_badgeView = qmui_badgeView; + /// iOS 26,需要改成block? + // self.qmui_selectedView.qmui_badgeView = qmui_badgeView; } - (__kindof UIView *)qmui_badgeView { @@ -187,6 +188,7 @@ - (void)setQmui_shouldShowUpdatesIndicator:(BOOL)qmui_shouldShowUpdatesIndicator [self updateViewDidSetBlockIfNeeded]; } self.qmui_view.qmui_shouldShowUpdatesIndicator = qmui_shouldShowUpdatesIndicator; + self.qmui_selectedView.qmui_shouldShowUpdatesIndicator = qmui_shouldShowUpdatesIndicator; } - (BOOL)qmui_shouldShowUpdatesIndicator { @@ -197,6 +199,7 @@ - (BOOL)qmui_shouldShowUpdatesIndicator { - (void)setQmui_updatesIndicatorColor:(UIColor *)qmui_updatesIndicatorColor { objc_setAssociatedObject(self, &kAssociatedObjectKey_updatesIndicatorColor, qmui_updatesIndicatorColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_updatesIndicatorColor = qmui_updatesIndicatorColor; + self.qmui_selectedView.qmui_updatesIndicatorColor = qmui_updatesIndicatorColor; } - (UIColor *)qmui_updatesIndicatorColor { @@ -207,6 +210,7 @@ - (UIColor *)qmui_updatesIndicatorColor { - (void)setQmui_updatesIndicatorSize:(CGSize)qmui_updatesIndicatorSize { objc_setAssociatedObject(self, &kAssociatedObjectKey_updatesIndicatorSize, [NSValue valueWithCGSize:qmui_updatesIndicatorSize], OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_updatesIndicatorSize = qmui_updatesIndicatorSize; + self.qmui_selectedView.qmui_updatesIndicatorSize = qmui_updatesIndicatorSize; } - (CGSize)qmui_updatesIndicatorSize { @@ -217,6 +221,7 @@ - (CGSize)qmui_updatesIndicatorSize { - (void)setQmui_updatesIndicatorOffset:(CGPoint)qmui_updatesIndicatorOffset { objc_setAssociatedObject(self, &kAssociatedObjectKey_updatesIndicatorOffset, @(qmui_updatesIndicatorOffset), OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_updatesIndicatorOffset = qmui_updatesIndicatorOffset; + self.qmui_selectedView.qmui_updatesIndicatorOffset = qmui_updatesIndicatorOffset; } - (CGPoint)qmui_updatesIndicatorOffset { @@ -227,6 +232,7 @@ - (CGPoint)qmui_updatesIndicatorOffset { - (void)setQmui_updatesIndicatorOffsetLandscape:(CGPoint)qmui_updatesIndicatorOffsetLandscape { objc_setAssociatedObject(self, &kAssociatedObjectKey_updatesIndicatorOffsetLandscape, @(qmui_updatesIndicatorOffsetLandscape), OBJC_ASSOCIATION_RETAIN_NONATOMIC); self.qmui_view.qmui_updatesIndicatorOffsetLandscape = qmui_updatesIndicatorOffsetLandscape; + self.qmui_selectedView.qmui_updatesIndicatorOffsetLandscape = qmui_updatesIndicatorOffsetLandscape; } - (CGPoint)qmui_updatesIndicatorOffsetLandscape { @@ -235,6 +241,8 @@ - (CGPoint)qmui_updatesIndicatorOffsetLandscape { - (void)setQmui_updatesIndicatorView:(__kindof UIView *)qmui_updatesIndicatorView { self.qmui_view.qmui_updatesIndicatorView = qmui_updatesIndicatorView; + /// iOS 26,需要改成block? + // self.qmui_selectedView.qmui_updatesIndicatorView = qmui_updatesIndicatorView; } - (UIView *)qmui_updatesIndicatorView { @@ -243,6 +251,7 @@ - (UIView *)qmui_updatesIndicatorView { - (void)setQmui_updatesIndicatorViewDidLayoutBlock:(void (^)(__kindof UIView * _Nonnull, __kindof UIView * _Nonnull))qmui_updatesIndicatorViewDidLayoutBlock { self.qmui_view.qmui_updatesIndicatorViewDidLayoutBlock = qmui_updatesIndicatorViewDidLayoutBlock; + self.qmui_selectedView.qmui_updatesIndicatorViewDidLayoutBlock = qmui_updatesIndicatorViewDidLayoutBlock; } - (void (^)(__kindof UIView * _Nonnull, __kindof UIView * _Nonnull))qmui_updatesIndicatorViewDidLayoutBlock { From 81b294011ac9c1218981a033949122c97cc80c13 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 12 Sep 2025 18:53:13 +0800 Subject: [PATCH 03/22] =?UTF-8?q?=E7=A7=BB=E9=99=A4=E4=B8=8D=E9=9C=80?= =?UTF-8?q?=E8=A6=81=E7=9A=84NSAssert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/UIKitExtensions/UIApplication+QMUI.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/QMUIKit/UIKitExtensions/UIApplication+QMUI.m b/QMUIKit/UIKitExtensions/UIApplication+QMUI.m index e3be8edd..53cfeb09 100644 --- a/QMUIKit/UIKitExtensions/UIApplication+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIApplication+QMUI.m @@ -80,7 +80,6 @@ - (nullable __kindof UIWindow *)qmui_delegateWindow { if (@available(iOS 13.0, *)) { [self.connectedScenes enumerateObjectsUsingBlock:^(UIScene *scene, BOOL *stop) { if ([scene isKindOfClass:UIWindowScene.class] && [scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication]) { - NSAssert(!scene.delegate || [scene.delegate respondsToSelector:@selector(window)], @"请检查是否有window属性"); if ([scene.delegate respondsToSelector:@selector(window)]) { delegateWindow = [scene.delegate performSelector:@selector(window)]; *stop = YES; @@ -88,7 +87,6 @@ - (nullable __kindof UIWindow *)qmui_delegateWindow { } }]; } - NSAssert(delegateWindow || !self.delegate || [self.delegate respondsToSelector:@selector(window)], @"请检查是否有window属性"); if (!delegateWindow && [self.delegate respondsToSelector:@selector(window)]) { delegateWindow = [self.delegate performSelector:@selector(window)]; } From 88ef04eb5fd2e07cdc7d3c282c606c9bb7775700 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Mon, 15 Sep 2025 19:01:59 +0800 Subject: [PATCH 04/22] =?UTF-8?q?QMUIPopupContainerView=EF=BC=8C=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=B8=80=E4=B8=AAAssert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/QMUIComponents/QMUIPopupContainerView.m | 1 + 1 file changed, 1 insertion(+) diff --git a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m index 6b491cdc..ca942410 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m +++ b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m @@ -101,6 +101,7 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { } - (void)setBackgroundView:(UIView *)backgroundView { + NSAssert(![backgroundView isKindOfClass:UIVisualEffectView.class], @"不支持UIVisualEffectView,请使用一个UIView代替,其subview可添加UIVisualEffectView"); if (_backgroundView && _backgroundView != backgroundView) { [_backgroundView removeFromSuperview]; } From 807b9676e259172ef7ace1c2c13a6d92859ab862 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Wed, 17 Sep 2025 13:39:28 +0800 Subject: [PATCH 05/22] =?UTF-8?q?1=E3=80=81QMUIKeyboardManager=20=E9=80=82?= =?UTF-8?q?=E9=85=8DiOS=2026=202=E3=80=81QMUIModalPresentationViewControll?= =?UTF-8?q?er=20=E5=85=BC=E5=AE=B9=E9=94=AE=E7=9B=98=E5=B7=B2=E7=BB=8F?= =?UTF-8?q?=E5=BC=B9=E8=B5=B7=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/QMUIComponents/QMUIKeyboardManager.m | 10 ++++++++++ .../QMUIModalPresentationViewController.m | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/QMUIKit/QMUIComponents/QMUIKeyboardManager.m b/QMUIKit/QMUIComponents/QMUIKeyboardManager.m index 8bd32f5a..68cd5b2b 100644 --- a/QMUIKit/QMUIComponents/QMUIKeyboardManager.m +++ b/QMUIKit/QMUIComponents/QMUIKeyboardManager.m @@ -874,6 +874,16 @@ + (UIView *)keyboardView { } + (UIView *)inputSetHostViewInWindow:(UIWindow *)window { + if (QMUIHelper.isUsedLiquidGlass) { + UIView *result = [[window.subviews qmui_firstMatchWithBlock:^BOOL(__kindof UIView * _Nonnull subview) { + return [NSStringFromClass(subview.class) isEqualToString:@"UITrackingWindowView"]; + }].subviews qmui_firstMatchWithBlock:^BOOL(__kindof UIView * _Nonnull subview) { + return [NSStringFromClass(subview.class) isEqualToString:@"UIKeyboardItemContainerView"] && subview.subviews.count; + }]; + if (result) { + return result; + } + } UIView *result = [[window.subviews qmui_firstMatchWithBlock:^BOOL(__kindof UIView * _Nonnull subview) { return [NSStringFromClass(subview.class) isEqualToString:@"UIInputSetContainerView"]; }].subviews qmui_firstMatchWithBlock:^BOOL(__kindof UIView * _Nonnull subview) { diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m index 9c3d52dc..5b36bde9 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m @@ -76,6 +76,8 @@ @interface QMUIModalPresentationViewController () @property(nonatomic, strong) QMUIKeyboardManager *keyboardManager; @property(nonatomic, assign) CGFloat keyboardHeight; @property(nonatomic, assign) BOOL avoidKeyboardLayout; +@property(nonatomic, assign) BOOL initializeKeyboardHeight; + @end @implementation QMUIModalPresentationViewController @@ -141,6 +143,16 @@ - (void)viewDidLoad { - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; + // 获取一次键盘高度,兼容键盘已经弹起时情况 + if (!self.initializeKeyboardHeight) { + self.initializeKeyboardHeight = YES; + + CGRect keyboardRect = [QMUIKeyboardManager convertKeyboardRect:QMUIKeyboardManager.currentKeyboardFrame toView:self.view]; + CGRect visibleRect = CGRectIntersection(CGRectFlatted(self.view.bounds), CGRectFlatted(keyboardRect)); + if (CGRectIsValidated(visibleRect)) { + self.keyboardHeight = visibleRect.size.height; + } + } self.dimmingView.frame = self.view.bounds; From 20229c8b77419e64cee53676be8eded924f3352e Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Wed, 24 Sep 2025 10:18:09 +0800 Subject: [PATCH 06/22] =?UTF-8?q?https://github.com/Tencent/QMUI=5FiOS/iss?= =?UTF-8?q?ues/1634=EF=BC=8C=E4=BF=AE=E5=A4=8D=E4=B8=9A=E5=8A=A1=E8=AE=BE?= =?UTF-8?q?=E7=BD=AEUIWindow=20overrideUserInterfaceStyle=20=E4=B8=BAlight?= =?UTF-8?q?=E6=88=96=E8=80=85dark=E5=90=8E=EF=BC=8C=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E8=A7=A6=E5=8F=91=E5=9B=9E=E8=B0=83=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIKitExtensions/UITraitCollection+QMUI.m | 45 +++++++++++-------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m index c49ea9ba..fb42cc26 100644 --- a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m @@ -59,33 +59,40 @@ + (void)_qmui_notifyUserInterfaceStyleWillChangeEvents:(UITraitCollection *)trai } } ++ (void)_qmui_notifyUserInterfaceStyleWithWindow:(UIWindow *)window { + static UIUserInterfaceStyle currentUserInterfaceStyle; + static NSSet *keyboardWindows; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + currentUserInterfaceStyle = -1; + keyboardWindows = [NSSet setWithArray:@[@"UIRemoteKeyboardWindow", @"UITextEffectsWindow", @"UITrackingWindowView"]]; + }); + + UITraitCollection *traitCollection = UITraitCollection.currentTraitCollection; + if (currentUserInterfaceStyle == traitCollection.userInterfaceStyle) { + return; + } + if ([keyboardWindows containsObject:NSStringFromClass(window.class)] || window != UIApplication.sharedApplication.qmui_delegateWindow) { + return; + } + currentUserInterfaceStyle = traitCollection.userInterfaceStyle; + + [self _qmui_notifyUserInterfaceStyleWillChangeEvents:traitCollection]; +} + + (void)_qmui_overrideTraitCollectionMethodIfNeeded { [QMUIHelper executeBlock:^{ - static UIUserInterfaceStyle qmui_lastNotifiedUserInterfaceStyle; - qmui_lastNotifiedUserInterfaceStyle = [UITraitCollection currentTraitCollection].userInterfaceStyle; - - static NSArray *keyboardWindows; - keyboardWindows = @[@"UIRemoteKeyboardWindow", @"UITextEffectsWindow"]; - /// https://github.com/Tencent/QMUI_iOS/issues/1634 - OverrideImplementation([UIViewController class], @selector(willTransitionToTraitCollection:withTransitionCoordinator:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIViewController *selfObject, UITraitCollection *newCollection, id coordinator) { + NSString *willTransitionSel = [NSString qmui_stringByConcat:@"_", @"willTransitionToTraitCollection:", @"withTransitionCoordinator:", nil]; + OverrideImplementation(UIWindow.class, NSSelectorFromString(willTransitionSel), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIWindow *selfObject, UITraitCollection *newCollection, id coordinator) { + + [UITraitCollection _qmui_notifyUserInterfaceStyleWithWindow:selfObject]; // call super void (*originSelectorIMP)(id, SEL, UITraitCollection *, id); originSelectorIMP = (void (*)(id, SEL, UITraitCollection *, id))originalIMPProvider(); originSelectorIMP(selfObject, originCMD, newCollection, coordinator); - - if (qmui_lastNotifiedUserInterfaceStyle == newCollection.userInterfaceStyle) { - return; - } - UIWindow *window = selfObject.viewIfLoaded.window; - if (!window || [keyboardWindows containsObject:NSStringFromClass(window.class)] || window != UIApplication.sharedApplication.qmui_delegateWindow) { - return; - } - qmui_lastNotifiedUserInterfaceStyle = newCollection.userInterfaceStyle; - - [self _qmui_notifyUserInterfaceStyleWillChangeEvents:newCollection]; }; }); } oncePerIdentifier:@"UITraitCollection addUserInterfaceStyleWillChangeObserver"]; From 4309c34589f7132c7556d3adb76083033e333b19 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Wed, 24 Sep 2025 16:55:47 +0800 Subject: [PATCH 07/22] =?UTF-8?q?https://github.com/Tencent/QMUI=5FiOS/iss?= =?UTF-8?q?ues/1668=EF=BC=8C=E4=BF=AE=E5=A4=8Dqmuisl=5FstepControls=20?= =?UTF-8?q?=E8=AE=BE=E7=BD=AEuserInteractionEnabled=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/UIKitExtensions/UISlider+QMUI.m | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/QMUIKit/UIKitExtensions/UISlider+QMUI.m b/QMUIKit/UIKitExtensions/UISlider+QMUI.m index 6278f2ab..b20f4e02 100644 --- a/QMUIKit/UIKitExtensions/UISlider+QMUI.m +++ b/QMUIKit/UIKitExtensions/UISlider+QMUI.m @@ -209,6 +209,8 @@ - (void)setQmui_numberOfSteps:(NSUInteger)numberOfSteps { [self.qmuisl_stepControls removeObjectAtIndex:i]; } } + [self qmuisl_updateStepControls]; + if (self.qmui_stepControlConfiguration) { [self.qmuisl_stepControls enumerateObjectsUsingBlock:^(QMUISliderStepControl * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { self.qmui_stepControlConfiguration(self, obj, idx); @@ -256,10 +258,7 @@ - (void)qmuisl_handleValueChanged:(UISlider *)slider { NSUInteger step = [slider qmuisl_stepWithValue:slider.value]; if (step != slider.qmuisl_precedingStep) { - [self.qmuisl_stepControls enumerateObjectsUsingBlock:^(QMUISliderStepControl * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - obj.userInteractionEnabled = idx != step;// 让 stepControl 不要影响 thumbView 的事件 - obj.indicator.hidden = idx == step; - }]; + [self qmuisl_updateStepControls]; if (slider.qmui_stepDidChangeBlock) { slider.qmui_stepDidChangeBlock(slider, slider.qmuisl_precedingStep); @@ -281,6 +280,13 @@ - (NSUInteger)qmuisl_stepWithValue:(float)value { return step; } +- (void)qmuisl_updateStepControls { + [self.qmuisl_stepControls enumerateObjectsUsingBlock:^(QMUISliderStepControl * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { + obj.userInteractionEnabled = idx != self.qmui_step;// 让 stepControl 不要影响 thumbView 的事件 + obj.indicator.hidden = idx == self.qmui_step; + }]; +} + - (void)qmuisl_swizzleForStepsIfNeeded { [QMUIHelper executeBlock:^{ OverrideImplementation([UISlider class], @selector(layoutSubviews), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { From df55545a243983a55ab514a374fad94f1781df2a Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Wed, 24 Sep 2025 16:58:15 +0800 Subject: [PATCH 08/22] =?UTF-8?q?https://github.com/Tencent/QMUI=5FiOS/iss?= =?UTF-8?q?ues/1668=EF=BC=8C=E4=BF=AE=E5=A4=8Dqmuisl=5FstepControls=20?= =?UTF-8?q?=E8=AE=BE=E7=BD=AEuserInteractionEnabled=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/UIKitExtensions/UISlider+QMUI.m | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/QMUIKit/UIKitExtensions/UISlider+QMUI.m b/QMUIKit/UIKitExtensions/UISlider+QMUI.m index b20f4e02..72b7da68 100644 --- a/QMUIKit/UIKitExtensions/UISlider+QMUI.m +++ b/QMUIKit/UIKitExtensions/UISlider+QMUI.m @@ -281,9 +281,10 @@ - (NSUInteger)qmuisl_stepWithValue:(float)value { } - (void)qmuisl_updateStepControls { + NSInteger step = self.qmui_step; [self.qmuisl_stepControls enumerateObjectsUsingBlock:^(QMUISliderStepControl * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { - obj.userInteractionEnabled = idx != self.qmui_step;// 让 stepControl 不要影响 thumbView 的事件 - obj.indicator.hidden = idx == self.qmui_step; + obj.userInteractionEnabled = idx != step;// 让 stepControl 不要影响 thumbView 的事件 + obj.indicator.hidden = idx == step; }]; } From 30ec9513a7c00cc74092df48e55354ac2cc0b5d1 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Thu, 25 Sep 2025 09:50:57 +0800 Subject: [PATCH 09/22] =?UTF-8?q?iOS=2026=EF=BC=8C=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E8=BD=AC=E5=9C=BA=E5=8A=A8=E7=94=BB=E7=9A=84=5FQMUITransitionN?= =?UTF-8?q?avigationBar=20y=E5=80=BC=E9=94=99=E8=AF=AF=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NavigationBarTransition/UINavigationBar+Transition.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m index 08c444dc..7e9ecaaa 100644 --- a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m +++ b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m @@ -294,7 +294,9 @@ - (void)updateLayout { UIView *backgroundView = self.originalNavigationBar.qmui_backgroundView; CGRect rect = [backgroundView.superview convertRect:backgroundView.frame toView:self.parentViewController.view]; [CALayer qmui_performWithoutAnimation:^{ - self.frame = CGRectSetX(rect, 0); // push/pop 过程中系统的导航栏转换过来的 x 可能是 112、-112 + // push/pop 过程中系统的导航栏转换过来的 x 可能是 112、-112 + // iOS 26上,y 可能是 -113 + self.frame = CGRectSetXY(rect, 0, 0); }]; } } From 440dcd026128f07d02e3123e191f4f9a963c37b5 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Thu, 25 Sep 2025 10:24:34 +0800 Subject: [PATCH 10/22] =?UTF-8?q?iOS=2026=EF=BC=8C=5FQMUITransitionNavigat?= =?UTF-8?q?ionBar=20=E8=AE=BE=E7=BD=AEframe=20=E5=90=8E=EF=BC=8C=E7=AB=8B?= =?UTF-8?q?=E5=8D=B3=E8=A7=A6=E5=8F=91layoutIfNeeded=EF=BC=8C=E5=90=A6?= =?UTF-8?q?=E5=88=99=E5=9C=A8=E6=89=8B=E5=8A=BF=E8=BF=94=E5=9B=9E=E6=97=B6?= =?UTF-8?q?=E4=BC=9A=E5=AF=BC=E8=87=B4=E6=9C=89=E4=B8=AA=E8=B7=9F=E9=9A=8F?= =?UTF-8?q?=E7=9A=84=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NavigationBarTransition/UINavigationBar+Transition.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m index 7e9ecaaa..3118ca56 100644 --- a/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m +++ b/QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.m @@ -276,6 +276,7 @@ - (void)layoutSubviews { [super layoutSubviews]; // 实测 iOS 11 Beta 1-5 里,自己 init 的 navigationBar.backgroundView.height 默认一直是 44,所以才加上这个兼容 self.qmui_backgroundView.frame = self.bounds; + [self.qmui_backgroundView layoutIfNeeded]; }]; } @@ -297,6 +298,7 @@ - (void)updateLayout { // push/pop 过程中系统的导航栏转换过来的 x 可能是 112、-112 // iOS 26上,y 可能是 -113 self.frame = CGRectSetXY(rect, 0, 0); + [self layoutIfNeeded]; }]; } } From 7cff3496408ae52bd55222cd1fcd2d45c8f26801 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Thu, 25 Sep 2025 18:11:39 +0800 Subject: [PATCH 11/22] =?UTF-8?q?QMUITheme,=20[UITraitCollection=20current?= =?UTF-8?q?TraitCollection]=20=E6=94=B9=E4=B8=BA=20UIScreen.mainScreen.tra?= =?UTF-8?q?itCollection=E6=9B=B4=E5=87=86=E7=A1=AE=E4=BA=9B=EF=BC=8C[UITra?= =?UTF-8?q?itCollection=20currentTraitCollection]=E6=98=AF=E5=9F=BA?= =?UTF-8?q?=E4=BA=8E=E5=BD=93=E5=89=8D=E4=B8=8A=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m | 2 +- QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m index 8a1b974b..d2ae0ffc 100644 --- a/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m +++ b/QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.m @@ -59,7 +59,7 @@ - (void)handleUserInterfaceStyleWillChangeEvent:(UITraitCollection *)traitCollec - (void)setRespondsSystemStyleAutomatically:(BOOL)respondsSystemStyleAutomatically { _respondsSystemStyleAutomatically = respondsSystemStyleAutomatically; if (_respondsSystemStyleAutomatically && self.identifierForTrait) { - self.currentThemeIdentifier = self.identifierForTrait([UITraitCollection currentTraitCollection]); + self.currentThemeIdentifier = self.identifierForTrait(UIScreen.mainScreen.traitCollection); } } diff --git a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m index fb42cc26..67bafbc2 100644 --- a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m @@ -64,11 +64,11 @@ + (void)_qmui_notifyUserInterfaceStyleWithWindow:(UIWindow *)window { static NSSet *keyboardWindows; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - currentUserInterfaceStyle = -1; + currentUserInterfaceStyle = UIUserInterfaceStyleUnspecified; keyboardWindows = [NSSet setWithArray:@[@"UIRemoteKeyboardWindow", @"UITextEffectsWindow", @"UITrackingWindowView"]]; }); - UITraitCollection *traitCollection = UITraitCollection.currentTraitCollection; + UITraitCollection *traitCollection = UIScreen.mainScreen.traitCollection; if (currentUserInterfaceStyle == traitCollection.userInterfaceStyle) { return; } From 8e2be1acd7a5e3e8fa29cb2c49394e91dfebf691 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 26 Sep 2025 09:23:03 +0800 Subject: [PATCH 12/22] =?UTF-8?q?QMUIModalPresentationViewController?= =?UTF-8?q?=E3=80=81QMUIPopupContainerView=E5=A2=9E=E5=8A=A0-=20(void)show?= =?UTF-8?q?InWindow:(nullable=20UIWindow=20*)window=EF=BC=8C=E7=94=A8?= =?UTF-8?q?=E4=BA=8E=E5=A4=9A=E4=B8=AAwindowScene=E7=9A=84=E6=83=85?= =?UTF-8?q?=E5=86=B5=E4=B8=8B=EF=BC=8C=20=E4=B8=9A=E5=8A=A1=E5=8F=AF?= =?UTF-8?q?=E4=BB=A5=E9=80=89=E6=8B=A9=E6=9F=90=E4=B8=AAwindow=E5=8E=BBsho?= =?UTF-8?q?w=EF=BC=8C=E9=81=BF=E5=85=8D=E8=A2=AB=E9=81=AE=E6=8C=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/QMUIComponents/QMUIAlertController.h | 3 +++ QMUIKit/QMUIComponents/QMUIAlertController.m | 6 +++++- QMUIKit/QMUIComponents/QMUIDialogViewController.h | 9 +++++++++ QMUIKit/QMUIComponents/QMUIDialogViewController.m | 6 +++++- .../QMUIModalPresentationViewController.h | 8 ++++++++ .../QMUIModalPresentationViewController.m | 7 ++++++- QMUIKit/QMUIComponents/QMUIMoreOperationController.h | 1 + QMUIKit/QMUIComponents/QMUIMoreOperationController.m | 6 +++++- QMUIKit/QMUIComponents/QMUIPopupContainerView.h | 2 ++ QMUIKit/QMUIComponents/QMUIPopupContainerView.m | 12 ++++++++---- 10 files changed, 52 insertions(+), 8 deletions(-) diff --git a/QMUIKit/QMUIComponents/QMUIAlertController.h b/QMUIKit/QMUIComponents/QMUIAlertController.h index ca1a82be..44877437 100644 --- a/QMUIKit/QMUIComponents/QMUIAlertController.h +++ b/QMUIKit/QMUIComponents/QMUIAlertController.h @@ -241,6 +241,9 @@ typedef NS_ENUM(NSInteger, QMUIAlertControllerStyle) { /// 显示`QMUIAlertController` - (void)showWithAnimated:(BOOL)animated; +/// 显示`QMUIAlertController` +- (void)showInWindow:(nullable UIWindow *)window animated:(BOOL)animated; + /// 隐藏`QMUIAlertController` - (void)hideWithAnimated:(BOOL)animated; diff --git a/QMUIKit/QMUIComponents/QMUIAlertController.m b/QMUIKit/QMUIComponents/QMUIAlertController.m index 32a201a2..658b021f 100644 --- a/QMUIKit/QMUIComponents/QMUIAlertController.m +++ b/QMUIKit/QMUIComponents/QMUIAlertController.m @@ -891,6 +891,10 @@ - (void)customModalPresentationControllerAnimation { } - (void)showWithAnimated:(BOOL)animated { + [self showInWindow:nil animated:animated]; +} + +- (void)showInWindow:(nullable UIWindow *)window animated:(BOOL)animated { if (self.willShow || self.showing) { return; } @@ -918,7 +922,7 @@ - (void)showWithAnimated:(BOOL)animated { __weak __typeof(self)weakSelf = self; - [self.modalPresentationViewController showWithAnimated:animated completion:^(BOOL finished) { + [self.modalPresentationViewController showInWindow:window animated:animated completion:^(BOOL finished) { weakSelf.dimmingView.alpha = 1; weakSelf.willShow = NO; weakSelf.showing = YES; diff --git a/QMUIKit/QMUIComponents/QMUIDialogViewController.h b/QMUIKit/QMUIComponents/QMUIDialogViewController.h index e1396ef7..e770a15d 100644 --- a/QMUIKit/QMUIComponents/QMUIDialogViewController.h +++ b/QMUIKit/QMUIComponents/QMUIDialogViewController.h @@ -127,6 +127,15 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)showWithAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL finished))completion; +/** + 显示弹窗 + + @param window 所在的window,默认为delegate.window + @param animated 是否以动画的形式显示 + @param completion 显示动画结束后的回调 + */ +- (void)showInWindow:(nullable UIWindow *)window animated:(BOOL)animated completion:(void (^ _Nullable)(BOOL finished))completion; + /** 以动画形式隐藏弹窗,等同于 [self hideWithAnimated:YES completion:nil] */ diff --git a/QMUIKit/QMUIComponents/QMUIDialogViewController.m b/QMUIKit/QMUIComponents/QMUIDialogViewController.m index 94b2c1dc..0e5acef7 100644 --- a/QMUIKit/QMUIComponents/QMUIDialogViewController.m +++ b/QMUIKit/QMUIComponents/QMUIDialogViewController.m @@ -373,10 +373,14 @@ - (void)show { } - (void)showWithAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { + [self showInWindow:nil animated:animated completion:completion]; +} + +- (void)showInWindow:(nullable UIWindow *)window animated:(BOOL)animated completion:(void (^ _Nullable)(BOOL finished))completion { self.modalPresentationViewController.contentViewMargins = self.dialogViewMargins; self.modalPresentationViewController.maximumContentViewWidth = self.maximumContentViewWidth; self.modalPresentationViewController.contentViewController = self; - [self.modalPresentationViewController showWithAnimated:YES completion:completion]; + [self.modalPresentationViewController showInWindow:window animated:animated completion:completion]; } - (void)hide { diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h index 7294f359..2ef65798 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.h @@ -250,6 +250,14 @@ typedef NS_ENUM(NSUInteger, QMUIModalPresentationAnimationStyle) { */ - (void)showWithAnimated:(BOOL)animated completion:(void (^ _Nullable)(BOOL finished))completion; +/** + * 将浮层以 UIWindow 的方式显示出来 + * @param window 所在的window,默认为delegate.window + * @param animated 是否以动画的形式显示 + * @param completion 显示动画结束后的回调 + */ +- (void)showInWindow:(nullable UIWindow *)window animated:(BOOL)animated completion:(void (^ _Nullable)(BOOL finished))completion; + /** * 将浮层隐藏掉 * @param animated 是否以动画的形式隐藏 diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m index 5b36bde9..36d08e19 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m @@ -512,6 +512,10 @@ - (void)showingAnimationWithCompletion:(void (^)(BOOL))completion { } - (void)showWithAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { + [self showInWindow:nil animated:animated completion:completion]; +} + +- (void)showInWindow:(nullable UIWindow *)window animated:(BOOL)animated completion:(void (^ _Nullable)(BOOL finished))completion { if (self.visible) return; self.visible = YES; @@ -520,7 +524,8 @@ - (void)showWithAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { self.appearCompletionBlock = completion; self.previousKeyWindow = UIApplication.sharedApplication.qmui_keyWindow; if (!self.window) { - self.window = [QMUIModalPresentationWindow qmui_windowWithWindowScene:UIApplication.sharedApplication.qmui_delegateWindow.windowScene]; + UIWindowScene *windowScene = window.windowScene ? : UIApplication.sharedApplication.qmui_delegateWindow.windowScene; + self.window = [QMUIModalPresentationWindow qmui_windowWithWindowScene:windowScene]; self.window.windowLevel = UIWindowLevelQMUIAlertView; self.window.backgroundColor = UIColorClear;// 避免横竖屏旋转时出现黑色 [self updateWindowStatusBarCapture]; diff --git a/QMUIKit/QMUIComponents/QMUIMoreOperationController.h b/QMUIKit/QMUIComponents/QMUIMoreOperationController.h index f84addaa..7c523b27 100644 --- a/QMUIKit/QMUIComponents/QMUIMoreOperationController.h +++ b/QMUIKit/QMUIComponents/QMUIMoreOperationController.h @@ -106,6 +106,7 @@ NS_ASSUME_NONNULL_BEGIN /// 弹出面板,一般在 init 完并且设置好 items 之后就调用这个接口来显示面板 - (void)showFromBottom; +- (void)showFromBottomInWindow:(nullable UIWindow *)window; /// 隐藏面板 - (void)hideToBottom; diff --git a/QMUIKit/QMUIComponents/QMUIMoreOperationController.m b/QMUIKit/QMUIComponents/QMUIMoreOperationController.m index 269b7a46..37260a1b 100644 --- a/QMUIKit/QMUIComponents/QMUIMoreOperationController.m +++ b/QMUIKit/QMUIComponents/QMUIMoreOperationController.m @@ -231,6 +231,10 @@ - (CGFloat)suitableColumnCountWithCount:(CGFloat)columnCount { } - (void)showFromBottom { + [self showFromBottomInWindow:nil]; +} + +- (void)showFromBottomInWindow:(nullable UIWindow *)window { if (self.showing || self.animating) { return; @@ -285,7 +289,7 @@ - (void)showFromBottom { }; self.animating = YES; - [modalPresentationViewController showWithAnimated:YES completion:NULL]; + [modalPresentationViewController showInWindow:window animated:YES completion:NULL]; } - (void)hideToBottom { diff --git a/QMUIKit/QMUIComponents/QMUIPopupContainerView.h b/QMUIKit/QMUIComponents/QMUIPopupContainerView.h index 2da5a690..0a425025 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupContainerView.h +++ b/QMUIKit/QMUIComponents/QMUIPopupContainerView.h @@ -176,6 +176,8 @@ typedef NS_ENUM(NSUInteger, QMUIPopupContainerViewLayoutAlignment) { - (void)showWithAnimated:(BOOL)animated; - (void)showWithAnimated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion; +- (void)showInWindow:(nullable UIWindow *)window animated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion; + - (void)hideWithAnimated:(BOOL)animated; - (void)hideWithAnimated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion; - (BOOL)isShowing; diff --git a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m index ca942410..364c70ab 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m +++ b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m @@ -690,10 +690,13 @@ - (void)showWithAnimated:(BOOL)animated { } - (void)showWithAnimated:(BOOL)animated completion:(void (^)(BOOL))completion { - + [self showInWindow:nil animated:animated completion:completion]; +} + +- (void)showInWindow:(nullable UIWindow *)window animated:(BOOL)animated completion:(void (^ __nullable)(BOOL finished))completion { BOOL isShowingByWindowMode = NO; if (!self.superview) { - [self initPopupContainerViewWindowIfNeeded]; + [self initPopupContainerViewWindowInWindow:window]; QMUICommonViewController *viewController = (QMUICommonViewController *)self.popupWindow.rootViewController; viewController.supportedOrientationMask = [QMUIHelper visibleViewController].supportedInterfaceOrientations; @@ -829,9 +832,10 @@ - (BOOL)isSubviewShowing:(UIView *)subview { return subview && !subview.hidden && subview.superview; } -- (void)initPopupContainerViewWindowIfNeeded { +- (void)initPopupContainerViewWindowInWindow:(nullable UIWindow *)window { if (!self.popupWindow) { - self.popupWindow = [QMUIPopupContainerViewWindow qmui_windowWithWindowScene:UIApplication.sharedApplication.qmui_delegateWindow.windowScene]; + UIWindowScene *windowScene = window.windowScene ? : UIApplication.sharedApplication.qmui_delegateWindow.windowScene; + self.popupWindow = [QMUIPopupContainerViewWindow qmui_windowWithWindowScene:windowScene]; self.popupWindow.qmui_capturesStatusBarAppearance = NO; self.popupWindow.backgroundColor = UIColorClear; self.popupWindow.windowLevel = UIWindowLevelQMUIAlertView; From 1cacb34ca6d30da1bf43dde8bf3442d43052cb34 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 26 Sep 2025 10:42:17 +0800 Subject: [PATCH 13/22] =?UTF-8?q?UIWindow=E9=9A=90=E8=97=8F=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E5=B0=86windowScene=E7=BD=AE=E7=A9=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m | 2 ++ QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m | 1 + QMUIKit/QMUIComponents/QMUIPopupContainerView.m | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m b/QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m index 679bda32..fefb5eb4 100644 --- a/QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m +++ b/QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.m @@ -133,6 +133,7 @@ + (void)show { [console initConsoleWindowIfNeeded]; console.consoleWindow.alpha = 0; console.consoleWindow.hidden = NO; + console.consoleWindow.windowScene = UIApplication.sharedApplication.qmui_delegateWindow.windowScene; }]; [UIView animateWithDuration:.25 delay:.2 options:QMUIViewAnimationOptionsCurveOut animations:^{ console.consoleWindow.alpha = 1; @@ -142,6 +143,7 @@ + (void)show { + (void)hide { [QMUIConsole sharedInstance].consoleWindow.hidden = YES; + [QMUIConsole sharedInstance].consoleWindow.windowScene = nil; } - (void)initConsoleWindowIfNeeded { diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m index 36d08e19..563d21b6 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m @@ -293,6 +293,7 @@ - (void)viewWillDisappear:(BOOL)animated { } } self.window.hidden = YES; + self.window.windowScene = nil; self.window.rootViewController = nil; self.previousKeyWindow = nil; [self endAppearanceTransition]; diff --git a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m index 364c70ab..d00bc746 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m +++ b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m @@ -805,8 +805,8 @@ - (void)hideCompletionWithWindowMode:(BOOL)windowMode completion:(void (^)(BOOL) [self removeFromSuperview]; self.popupWindow.rootViewController = nil; - self.popupWindow.windowScene = nil; self.popupWindow.hidden = YES; + self.popupWindow.windowScene = nil; self.popupWindow = nil; } else { self.hidden = YES; From 57220704a7f3350edae7115ba8e11e817e9dfc3e Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 26 Sep 2025 12:05:58 +0800 Subject: [PATCH 14/22] =?UTF-8?q?QMUIModalPresentationViewController?= =?UTF-8?q?=E9=80=82=E9=85=8D=E9=94=AE=E7=9B=98=E5=B7=B2=E7=BB=8F=E5=BC=B9?= =?UTF-8?q?=E8=B5=B7=E7=9A=84=E6=83=85=E5=86=B5=EF=BC=8C=E9=9A=8F=E5=90=8E?= =?UTF-8?q?=E5=8F=88=E8=90=BD=E4=B8=8B=E7=9A=84=E6=83=85=E5=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../QMUIModalPresentationViewController.m | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m index 563d21b6..06ab6a54 100644 --- a/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m +++ b/QMUIKit/QMUIComponents/QMUIModalPresentationViewController.m @@ -76,7 +76,7 @@ @interface QMUIModalPresentationViewController () @property(nonatomic, strong) QMUIKeyboardManager *keyboardManager; @property(nonatomic, assign) CGFloat keyboardHeight; @property(nonatomic, assign) BOOL avoidKeyboardLayout; -@property(nonatomic, assign) BOOL initializeKeyboardHeight; +@property(nonatomic, assign) CGFloat initializeKeyboardHeight; // 默认为-1 @end @@ -99,6 +99,7 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { - (void)didInitialize { [self qmui_applyAppearance]; + self.initializeKeyboardHeight = -1; self.shouldDimmedAppAutomatically = YES; self.onlyRespondsToKeyboardEventFromDescendantViews = YES; self.shouldBecomeKeyWindow = YES; @@ -144,14 +145,13 @@ - (void)viewDidLoad { - (void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; // 获取一次键盘高度,兼容键盘已经弹起时情况 - if (!self.initializeKeyboardHeight) { - self.initializeKeyboardHeight = YES; - + if (self.initializeKeyboardHeight == -1) { CGRect keyboardRect = [QMUIKeyboardManager convertKeyboardRect:QMUIKeyboardManager.currentKeyboardFrame toView:self.view]; CGRect visibleRect = CGRectIntersection(CGRectFlatted(self.view.bounds), CGRectFlatted(keyboardRect)); if (CGRectIsValidated(visibleRect)) { self.keyboardHeight = visibleRect.size.height; } + self.initializeKeyboardHeight = self.keyboardHeight; } self.dimmingView.frame = self.view.bounds; @@ -691,12 +691,14 @@ - (BOOL)isShowingPresentedViewController { #pragma mark - - (void)keyboardWillChangeFrameWithUserInfo:(QMUIKeyboardUserInfo *)keyboardUserInfo { - if (self.onlyRespondsToKeyboardEventFromDescendantViews) { + if (self.onlyRespondsToKeyboardEventFromDescendantViews && self.initializeKeyboardHeight == 0) { UIResponder *firstResponder = keyboardUserInfo.targetResponder; if (!firstResponder || !([firstResponder isKindOfClass:[UIView class]] && [(UIView *)firstResponder isDescendantOfView:self.view])) { return; } } + self.initializeKeyboardHeight = 0; + CGFloat keyboardHeight = [keyboardUserInfo heightInView:self.view]; if (self.keyboardHeight != keyboardHeight) { self.keyboardHeight = keyboardHeight; From 1bc60181d673f010b253bd41be9e7f6d052f1737 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 26 Sep 2025 17:17:34 +0800 Subject: [PATCH 15/22] =?UTF-8?q?QMUIPopupContainerView=EF=BC=8C=E5=BD=93?= =?UTF-8?q?=E4=B8=8A=E4=B8=8B=E6=88=96=E8=80=85=E5=B7=A6=E5=8F=B3=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E8=B6=B3=E5=A4=9F=E7=9A=84=E7=A9=BA=E9=97=B4=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E4=B8=8D=E8=83=BD=E7=9B=B4=E6=8E=A5=E6=94=B9=E5=8F=98?= =?UTF-8?q?maximumHeight=E6=88=96=E8=80=85maximumWidth=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E7=84=B6=E5=BD=93=E6=9C=89=E8=B6=B3=E5=A4=9F=E7=9A=84=E7=A9=BA?= =?UTF-8?q?=E9=97=B4=E6=97=B6=EF=BC=8C=E4=BC=9A=E5=9B=A0maximumHeight/maxi?= =?UTF-8?q?mumWidth=E8=AE=A1=E7=AE=97=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/QMUIComponents/QMUIPopupContainerView.h | 2 +- QMUIKit/QMUIComponents/QMUIPopupContainerView.m | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/QMUIKit/QMUIComponents/QMUIPopupContainerView.h b/QMUIKit/QMUIComponents/QMUIPopupContainerView.h index 0a425025..bd6a3e4f 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupContainerView.h +++ b/QMUIKit/QMUIComponents/QMUIPopupContainerView.h @@ -110,7 +110,7 @@ typedef NS_ENUM(NSUInteger, QMUIPopupContainerViewLayoutAlignment) { /// 最小宽度(指整个控件的宽度,而不是contentView部分),默认为0 @property(nonatomic, assign) CGFloat minimumWidth UI_APPEARANCE_SELECTOR; -/// 最大高度(指整个控件的高度,而不是contentView部分),默认为CGFLOAT_MAX,会在布局时被动态修改。 +/// 最大高度(指整个控件的高度,而不是contentView部分),默认为CGFLOAT_MAX @property(nonatomic, assign) CGFloat maximumHeight UI_APPEARANCE_SELECTOR; /// 最小高度(指整个控件的高度,而不是contentView部分),默认为0 diff --git a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m index d00bc746..047fe754 100644 --- a/QMUIKit/QMUIComponents/QMUIPopupContainerView.m +++ b/QMUIKit/QMUIComponents/QMUIPopupContainerView.m @@ -531,11 +531,11 @@ - (void)layoutWithTargetRect:(CGRect)targetRect { // 上下都没有足够的空间,所以要调整maximumHeight CGFloat maximumHeightAbove = CGRectGetMinY(targetRect) - CGRectGetMinY(containerRect) - self.distanceBetweenSource - self.safetyMarginsAvoidSafeAreaInsets.top; CGFloat maximumHeightBelow = CGRectGetMaxY(containerRect) - self.safetyMarginsAvoidSafeAreaInsets.bottom - self.distanceBetweenSource - CGRectGetMaxY(targetRect); - self.maximumHeight = MAX(self.minimumHeight, MAX(maximumHeightAbove, maximumHeightBelow)); - tipSize.height = self.maximumHeight; + CGFloat maximumHeight = MAX(self.minimumHeight, MAX(maximumHeightAbove, maximumHeightBelow)); + tipSize.height = maximumHeight; _currentLayoutDirection = maximumHeightAbove > maximumHeightBelow ? QMUIPopupContainerViewLayoutDirectionAbove : QMUIPopupContainerViewLayoutDirectionBelow; - QMUILog(NSStringFromClass(self.class), @"%@, 因为上下都不够空间,所以最大高度被强制改为%@, 位于目标的%@", self, @(self.maximumHeight), maximumHeightAbove > maximumHeightBelow ? @"上方" : @"下方"); + QMUILog(NSStringFromClass(self.class), @"%@, 因为上下都不够空间,所以最大高度被强制改为%@, 位于目标的%@", self, @(maximumHeight), maximumHeightAbove > maximumHeightBelow ? @"上方" : @"下方"); } else if (_currentLayoutDirection == QMUIPopupContainerViewLayoutDirectionAbove && !canShowAtAbove) { _currentLayoutDirection = QMUIPopupContainerViewLayoutDirectionBelow; @@ -599,11 +599,11 @@ - (void)layoutWithTargetRect:(CGRect)targetRect { // 左右都没有足够的空间,所以要调整maximumWidth CGFloat maximumWidthLeft = CGRectGetMinX(targetRect) - CGRectGetMinX(containerRect) - self.distanceBetweenSource - self.safetyMarginsAvoidSafeAreaInsets.left; CGFloat maximumWidthRight = CGRectGetMaxX(containerRect) - self.safetyMarginsAvoidSafeAreaInsets.right - self.distanceBetweenSource - CGRectGetMaxX(targetRect); - self.maximumWidth = MAX(self.minimumWidth, MAX(maximumWidthLeft, maximumWidthRight)); - tipSize.width = self.maximumWidth; + CGFloat maximumWidth = MAX(self.minimumWidth, MAX(maximumWidthLeft, maximumWidthRight)); + tipSize.width = maximumWidth; _currentLayoutDirection = maximumWidthLeft > maximumWidthRight ? QMUIPopupContainerViewLayoutDirectionLeft : QMUIPopupContainerViewLayoutDirectionRight; - QMUILog(NSStringFromClass(self.class), @"%@, 因为左右都不够空间,所以最大宽度被强制改为%@, 位于目标的%@", self, @(self.maximumWidth), maximumWidthLeft > maximumWidthRight ? @"左边" : @"右边"); + QMUILog(NSStringFromClass(self.class), @"%@, 因为左右都不够空间,所以最大宽度被强制改为%@, 位于目标的%@", self, @(maximumWidth), maximumWidthLeft > maximumWidthRight ? @"左边" : @"右边"); } else if (_currentLayoutDirection == QMUIPopupContainerViewLayoutDirectionLeft && !canShowAtLeft) { _currentLayoutDirection = QMUIPopupContainerViewLayoutDirectionLeft; From a977cbbafb00bef27d552dcc0209861792d7ab16 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Thu, 16 Oct 2025 15:56:36 +0800 Subject: [PATCH 16/22] =?UTF-8?q?QMUITheme,=20=E4=BD=BF=E7=94=A8UIScreen?= =?UTF-8?q?=E7=9A=84=5FsetDefaultTraitCollection=E6=9B=B4=E5=87=86?= =?UTF-8?q?=E7=A1=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIKitExtensions/UITraitCollection+QMUI.m | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m index 67bafbc2..864e00c8 100644 --- a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m @@ -59,22 +59,11 @@ + (void)_qmui_notifyUserInterfaceStyleWillChangeEvents:(UITraitCollection *)trai } } -+ (void)_qmui_notifyUserInterfaceStyleWithWindow:(UIWindow *)window { - static UIUserInterfaceStyle currentUserInterfaceStyle; - static NSSet *keyboardWindows; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - currentUserInterfaceStyle = UIUserInterfaceStyleUnspecified; - keyboardWindows = [NSSet setWithArray:@[@"UIRemoteKeyboardWindow", @"UITextEffectsWindow", @"UITrackingWindowView"]]; - }); - - UITraitCollection *traitCollection = UIScreen.mainScreen.traitCollection; ++ (void)_qmui_notifyUserInterfaceStyleWillChangeForTraitCollection:(UITraitCollection *)traitCollection { + static UIUserInterfaceStyle currentUserInterfaceStyle = UIUserInterfaceStyleUnspecified; if (currentUserInterfaceStyle == traitCollection.userInterfaceStyle) { return; } - if ([keyboardWindows containsObject:NSStringFromClass(window.class)] || window != UIApplication.sharedApplication.qmui_delegateWindow) { - return; - } currentUserInterfaceStyle = traitCollection.userInterfaceStyle; [self _qmui_notifyUserInterfaceStyleWillChangeEvents:traitCollection]; @@ -83,16 +72,18 @@ + (void)_qmui_notifyUserInterfaceStyleWithWindow:(UIWindow *)window { + (void)_qmui_overrideTraitCollectionMethodIfNeeded { [QMUIHelper executeBlock:^{ /// https://github.com/Tencent/QMUI_iOS/issues/1634 - NSString *willTransitionSel = [NSString qmui_stringByConcat:@"_", @"willTransitionToTraitCollection:", @"withTransitionCoordinator:", nil]; - OverrideImplementation(UIWindow.class, NSSelectorFromString(willTransitionSel), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIWindow *selfObject, UITraitCollection *newCollection, id coordinator) { + NSString *willChangeTraitCollection = [NSString qmui_stringByConcat:@"_", @"setDefault", @"TraitCollection:", nil]; + OverrideImplementation([UIScreen class], NSSelectorFromString(willChangeTraitCollection), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIScreen *selfObject, UITraitCollection *traitCollection) { - [UITraitCollection _qmui_notifyUserInterfaceStyleWithWindow:selfObject]; + if (selfObject == UIScreen.mainScreen) { + [UITraitCollection _qmui_notifyUserInterfaceStyleWillChangeForTraitCollection:traitCollection]; + } // call super - void (*originSelectorIMP)(id, SEL, UITraitCollection *, id); - originSelectorIMP = (void (*)(id, SEL, UITraitCollection *, id))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, newCollection, coordinator); + void (*originSelectorIMP)(id, SEL, UITraitCollection *); + originSelectorIMP = (void (*)(id, SEL, UITraitCollection *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, traitCollection); }; }); } oncePerIdentifier:@"UITraitCollection addUserInterfaceStyleWillChangeObserver"]; From c8691cc9c98df6536775ed6aef91c588fa484315 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 17 Oct 2025 11:34:57 +0800 Subject: [PATCH 17/22] =?UTF-8?q?qmui=5FaddUserInterfaceStyleWillChangeObs?= =?UTF-8?q?erver=EF=BC=8Cmac=E4=B8=8A=E4=BD=BF=E7=94=A8UIScreen=E7=9A=84?= =?UTF-8?q?=5FsetDefaultTraitCollection=EF=BC=8C=E5=85=B6=E4=BB=96?= =?UTF-8?q?=E5=B9=B3=E5=8F=B0=E4=BD=BF=E7=94=A8=5FparentWillTransitionToTr?= =?UTF-8?q?aitCollection:?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UIKitExtensions/UITraitCollection+QMUI.m | 47 +++++++++++++------ 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m index 864e00c8..d64ac93d 100644 --- a/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m +++ b/QMUIKit/UIKitExtensions/UITraitCollection+QMUI.m @@ -59,7 +59,7 @@ + (void)_qmui_notifyUserInterfaceStyleWillChangeEvents:(UITraitCollection *)trai } } -+ (void)_qmui_notifyUserInterfaceStyleWillChangeForTraitCollection:(UITraitCollection *)traitCollection { ++ (void)_qmui_setUserInterfaceStyleForTraitCollection:(UITraitCollection *)traitCollection { static UIUserInterfaceStyle currentUserInterfaceStyle = UIUserInterfaceStyleUnspecified; if (currentUserInterfaceStyle == traitCollection.userInterfaceStyle) { return; @@ -72,20 +72,37 @@ + (void)_qmui_notifyUserInterfaceStyleWillChangeForTraitCollection:(UITraitColle + (void)_qmui_overrideTraitCollectionMethodIfNeeded { [QMUIHelper executeBlock:^{ /// https://github.com/Tencent/QMUI_iOS/issues/1634 - NSString *willChangeTraitCollection = [NSString qmui_stringByConcat:@"_", @"setDefault", @"TraitCollection:", nil]; - OverrideImplementation([UIScreen class], NSSelectorFromString(willChangeTraitCollection), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { - return ^(UIScreen *selfObject, UITraitCollection *traitCollection) { - - if (selfObject == UIScreen.mainScreen) { - [UITraitCollection _qmui_notifyUserInterfaceStyleWillChangeForTraitCollection:traitCollection]; - } - - // call super - void (*originSelectorIMP)(id, SEL, UITraitCollection *); - originSelectorIMP = (void (*)(id, SEL, UITraitCollection *))originalIMPProvider(); - originSelectorIMP(selfObject, originCMD, traitCollection); - }; - }); + if (QMUIHelper.isMac) { + NSString *willChangeTraitCollection = [NSString qmui_stringByConcat:@"_", @"setDefault", @"TraitCollection:", nil]; + OverrideImplementation([UIScreen class], NSSelectorFromString(willChangeTraitCollection), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIScreen *selfObject, UITraitCollection *traitCollection) { + + if (selfObject == UIScreen.mainScreen) { + [UITraitCollection _qmui_setUserInterfaceStyleForTraitCollection:traitCollection]; + } + + // call super + void (*originSelectorIMP)(id, SEL, UITraitCollection *); + originSelectorIMP = (void (*)(id, SEL, UITraitCollection *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, traitCollection); + }; + }); + } else { + NSString *willChangeTraitCollection = [NSString qmui_stringByConcat:@"_", @"parent", @"WillTransitionTo", @"TraitCollection:", nil]; + OverrideImplementation([UIWindow class], NSSelectorFromString(willChangeTraitCollection), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) { + return ^(UIWindow *selfObject, UITraitCollection *traitCollection) { + + if (selfObject == UIApplication.sharedApplication.qmui_delegateWindow) { + [UITraitCollection _qmui_setUserInterfaceStyleForTraitCollection:traitCollection]; + } + + // call super + void (*originSelectorIMP)(id, SEL, UITraitCollection *); + originSelectorIMP = (void (*)(id, SEL, UITraitCollection *))originalIMPProvider(); + originSelectorIMP(selfObject, originCMD, traitCollection); + }; + }); + } } oncePerIdentifier:@"UITraitCollection addUserInterfaceStyleWillChangeObserver"]; } From ebb9d3a668442d867531ab6672861e918f54c65f Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Thu, 23 Oct 2025 09:50:58 +0800 Subject: [PATCH 18/22] https://github.com/Tencent/QMUI_iOS/issues/1680 --- QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m b/QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m index 28944c00..2950fe36 100644 --- a/QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m +++ b/QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m @@ -54,6 +54,10 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { } - (void)didInitialize { + // https://github.com/Tencent/QMUI_iOS/issues/1680 + [self imageView]; + [self titleLabel]; + // 默认接管highlighted和disabled的表现,去掉系统默认的表现 self.adjustsImageWhenHighlighted = NO; self.adjustsImageWhenDisabled = NO; @@ -118,7 +122,17 @@ - (QMUILayouterItem *)generateLayouterForLayout:(BOOL)forLayout { ][self.contentVerticalAlignment] integerValue]; BOOL isImageViewShowing = !!self.currentImage; - QMUILayouterItem *image = [QMUILayouterItem itemWithView:isImageViewShowing ? (forLayout ? self._qmui_imageView : self.imageView) : nil margin:self.imageEdgeInsets]; + // https://github.com/Tencent/QMUI_iOS/issues/1680 + UIImageView *imageView = nil; + if (isImageViewShowing) { + if (forLayout) { + imageView = self._qmui_imageView; + } else { + // 加self.imageView是为了兜底,理论上_qmui_imageView不会为nil + imageView = self._qmui_imageView ? : self.imageView; + } + } + QMUILayouterItem *image = [QMUILayouterItem itemWithView:imageView margin:self.imageEdgeInsets]; image.visibleBlock = ^BOOL(QMUILayouterItem * _Nonnull aItem) { return !!weakSelf.currentImage; }; From 12eb8d39ee1ea5b57428a6007bdf22a384821aca Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 24 Oct 2025 15:20:55 +0800 Subject: [PATCH 19/22] Revert "https://github.com/Tencent/QMUI_iOS/issues/1680" This reverts commit ebb9d3a668442d867531ab6672861e918f54c65f. --- QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m b/QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m index 2950fe36..28944c00 100644 --- a/QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m +++ b/QMUIKit/QMUIComponents/QMUIButton/QMUIButton.m @@ -54,10 +54,6 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { } - (void)didInitialize { - // https://github.com/Tencent/QMUI_iOS/issues/1680 - [self imageView]; - [self titleLabel]; - // 默认接管highlighted和disabled的表现,去掉系统默认的表现 self.adjustsImageWhenHighlighted = NO; self.adjustsImageWhenDisabled = NO; @@ -122,17 +118,7 @@ - (QMUILayouterItem *)generateLayouterForLayout:(BOOL)forLayout { ][self.contentVerticalAlignment] integerValue]; BOOL isImageViewShowing = !!self.currentImage; - // https://github.com/Tencent/QMUI_iOS/issues/1680 - UIImageView *imageView = nil; - if (isImageViewShowing) { - if (forLayout) { - imageView = self._qmui_imageView; - } else { - // 加self.imageView是为了兜底,理论上_qmui_imageView不会为nil - imageView = self._qmui_imageView ? : self.imageView; - } - } - QMUILayouterItem *image = [QMUILayouterItem itemWithView:imageView margin:self.imageEdgeInsets]; + QMUILayouterItem *image = [QMUILayouterItem itemWithView:isImageViewShowing ? (forLayout ? self._qmui_imageView : self.imageView) : nil margin:self.imageEdgeInsets]; image.visibleBlock = ^BOOL(QMUILayouterItem * _Nonnull aItem) { return !!weakSelf.currentImage; }; From 3ef2a3d4f54feb89260bf6e7d540b81ef97c658c Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 24 Oct 2025 17:38:33 +0800 Subject: [PATCH 20/22] =?UTF-8?q?=E6=9A=82=E6=97=B6=E6=80=A7=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dhttps://github.com/Tencent/QMUI=5FiOS/issues/1680?= =?UTF-8?q?=EF=BC=8C=E6=AD=A4=E4=B8=BAUIKit=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/UIKitExtensions/NSObject+QMUI.m | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/QMUIKit/UIKitExtensions/NSObject+QMUI.m b/QMUIKit/UIKitExtensions/NSObject+QMUI.m index 9fd21b90..6ee5cd79 100644 --- a/QMUIKit/UIKitExtensions/NSObject+QMUI.m +++ b/QMUIKit/UIKitExtensions/NSObject+QMUI.m @@ -520,8 +520,18 @@ + (void)load { originSelectorIMP = (id (*)(id, SEL, NSExceptionName name, NSString *, ...))originalIMPProvider(); va_list args; va_start(args, format); - NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; - originSelectorIMP(selfObject, originCMD, raise, reason); + NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; + BOOL shouldCallSuper = YES; + // https://github.com/Tencent/QMUI_iOS/issues/1680 + if (@available(iOS 26.0, *)) { + if (raise == NSInternalInconsistencyException && [reason hasPrefix:@"The layout constraints still need update after sending -updateConstraints to <_UINavigationBarTitleControl"]) { + QMUILogWarn(@"NSObject (QMUI)", @"iOS 26.0会因约束问题触发_UINavigationBarTitleControl的 NSException,详情见:https://github.com/Tencent/QMUI_iOS/issues/1680"); + shouldCallSuper = NO; + } + } + if (shouldCallSuper) { + originSelectorIMP(selfObject, originCMD, raise, reason); + } va_end(args); }; }); From a84b36c4ba07258c05dbbd452b27193e5d346a67 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Fri, 31 Oct 2025 10:32:04 +0800 Subject: [PATCH 21/22] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=8E=B7=E5=8F=96hx=5F?= =?UTF-8?q?keyWindow=E7=9A=84=E9=80=BB=E8=BE=91=EF=BC=8C=E5=BD=93connected?= =?UTF-8?q?Scenes=E9=87=8C=E6=89=BE=E4=B8=8D=E5=88=B0keyWindow=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8UIApplication.shared.keyWindow?= =?UTF-8?q?=E5=8E=BB=E8=8E=B7=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/UIKitExtensions/UIApplication+QMUI.m | 46 +++++++++++--------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/QMUIKit/UIKitExtensions/UIApplication+QMUI.m b/QMUIKit/UIKitExtensions/UIApplication+QMUI.m index 53cfeb09..934c0d7a 100644 --- a/QMUIKit/UIKitExtensions/UIApplication+QMUI.m +++ b/QMUIKit/UIKitExtensions/UIApplication+QMUI.m @@ -47,14 +47,12 @@ - (void)qmui_handleDidFinishLaunchingNotification:(NSNotification *)notification - (NSArray<__kindof UIWindow *> *)qmui_windows { __block NSArray *windows = nil; - if (@available(iOS 13.0, *)) { - [self.connectedScenes enumerateObjectsUsingBlock:^(UIScene *scene, BOOL *stop) { - if ([scene isKindOfClass:UIWindowScene.class] && [scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication]) { - windows = [(UIWindowScene *)scene windows]; - *stop = YES; - } - }]; - } + [self.connectedScenes enumerateObjectsUsingBlock:^(UIScene *scene, BOOL *stop) { + if ([scene isKindOfClass:UIWindowScene.class] && [scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication]) { + windows = [(UIWindowScene *)scene windows]; + *stop = YES; + } + }]; if (!windows || windows.count == 0) { windows = self.windows; } @@ -63,12 +61,22 @@ - (void)qmui_handleDidFinishLaunchingNotification:(NSNotification *)notification - (nullable __kindof UIWindow *)qmui_keyWindow { __block UIWindow *keyWindow = nil; - [self.qmui_windows enumerateObjectsUsingBlock:^(__kindof UIWindow *window, NSUInteger idx, BOOL *stop) { - if (window.isKeyWindow && !window.isHidden) { - keyWindow = window; + [self.connectedScenes enumerateObjectsUsingBlock:^(UIScene *scene, BOOL *stop) { + if ([scene isKindOfClass:UIWindowScene.class] && [scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication]) { + [[(UIWindowScene *)scene windows] enumerateObjectsUsingBlock:^(UIWindow *window, NSUInteger idx, BOOL *substop) { + if (window.isKeyWindow && !window.isHidden) { + keyWindow = window; + *substop = YES; + } + }]; *stop = YES; } }]; + if (!keyWindow) { + BeginIgnoreDeprecatedWarning + keyWindow = self.keyWindow; + EndIgnoreDeprecatedWarning + } if (!keyWindow) { keyWindow = self.qmui_delegateWindow; } @@ -77,16 +85,14 @@ - (nullable __kindof UIWindow *)qmui_keyWindow { - (nullable __kindof UIWindow *)qmui_delegateWindow { __block UIWindow *delegateWindow = nil; - if (@available(iOS 13.0, *)) { - [self.connectedScenes enumerateObjectsUsingBlock:^(UIScene *scene, BOOL *stop) { - if ([scene isKindOfClass:UIWindowScene.class] && [scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication]) { - if ([scene.delegate respondsToSelector:@selector(window)]) { - delegateWindow = [scene.delegate performSelector:@selector(window)]; - *stop = YES; - } + [self.connectedScenes enumerateObjectsUsingBlock:^(UIScene *scene, BOOL *stop) { + if ([scene isKindOfClass:UIWindowScene.class] && [scene.session.role isEqualToString:UIWindowSceneSessionRoleApplication]) { + if ([scene.delegate respondsToSelector:@selector(window)]) { + delegateWindow = [scene.delegate performSelector:@selector(window)]; + *stop = YES; } - }]; - } + } + }]; if (!delegateWindow && [self.delegate respondsToSelector:@selector(window)]) { delegateWindow = [self.delegate performSelector:@selector(window)]; } From d1c7bfd32275285ee24fbe47d92b4a23942300a8 Mon Sep 17 00:00:00 2001 From: jiasong <593908937@qq.com> Date: Mon, 3 Nov 2025 09:15:05 +0800 Subject: [PATCH 22/22] =?UTF-8?q?iOS=2026=E6=B6=B2=E6=80=81=E7=8E=BB?= =?UTF-8?q?=E7=92=83=E4=B8=8B=EF=BC=8C=E8=BD=AC=E5=9C=BA=E5=8A=A8=E7=94=BB?= =?UTF-8?q?=E5=8F=AF=E4=BB=A5=E8=A2=AB=E6=89=93=E6=96=AD=EF=BC=8C=E4=B8=8D?= =?UTF-8?q?=E9=9C=80=E8=A6=81QMUILogWarn=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- QMUIKit/UIKitExtensions/NSObject+QMUI.m | 2 +- .../UINavigationController+QMUI.m | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/QMUIKit/UIKitExtensions/NSObject+QMUI.m b/QMUIKit/UIKitExtensions/NSObject+QMUI.m index 6ee5cd79..1ec4475d 100644 --- a/QMUIKit/UIKitExtensions/NSObject+QMUI.m +++ b/QMUIKit/UIKitExtensions/NSObject+QMUI.m @@ -523,7 +523,7 @@ + (void)load { NSString *reason = [[NSString alloc] initWithFormat:format arguments:args]; BOOL shouldCallSuper = YES; // https://github.com/Tencent/QMUI_iOS/issues/1680 - if (@available(iOS 26.0, *)) { + if (QMUIHelper.isUsedLiquidGlass) { if (raise == NSInternalInconsistencyException && [reason hasPrefix:@"The layout constraints still need update after sending -updateConstraints to <_UINavigationBarTitleControl"]) { QMUILogWarn(@"NSObject (QMUI)", @"iOS 26.0会因约束问题触发_UINavigationBarTitleControl的 NSException,详情见:https://github.com/Tencent/QMUI_iOS/issues/1680"); shouldCallSuper = NO; diff --git a/QMUIKit/UIKitExtensions/UINavigationController+QMUI.m b/QMUIKit/UIKitExtensions/UINavigationController+QMUI.m index f5a15064..342d037a 100644 --- a/QMUIKit/UIKitExtensions/UINavigationController+QMUI.m +++ b/QMUIKit/UIKitExtensions/UINavigationController+QMUI.m @@ -245,7 +245,11 @@ + (void)load { QMUINavigationAction action = selfObject.qmui_navigationAction; if (action != QMUINavigationActionUnknow) { - QMUILogWarn(@"UINavigationController (QMUI)", @"popViewController 时上一次的转场尚未完成,系统会忽略本次 pop,等上一次转场完成后再重新执行 pop, viewControllers = %@", selfObject.viewControllers); + if (QMUIHelper.isUsedLiquidGlass) { + // iOS 26液态玻璃下,转场动画可以被打断 + } else { + QMUILogWarn(@"UINavigationController (QMUI)", @"popViewController 时上一次的转场尚未完成,系统会忽略本次 pop,等上一次转场完成后再重新执行 pop, viewControllers = %@", selfObject.viewControllers); + } } BOOL willPopActually = selfObject.viewControllers.count > 1 && action == QMUINavigationActionUnknow;// 系统文档里说 rootViewController 是不能被 pop 的,当只剩下 rootViewController 时当前方法什么事都不会做 @@ -321,7 +325,11 @@ + (void)load { QMUINavigationAction action = selfObject.qmui_navigationAction; if (action != QMUINavigationActionUnknow) { - QMUILogWarn(@"UINavigationController (QMUI)", @"popToViewController 时上一次的转场尚未完成,系统会忽略本次 pop,等上一次转场完成后再重新执行 pop, currentViewControllers = %@, viewController = %@", selfObject.viewControllers, viewController); + if (QMUIHelper.isUsedLiquidGlass) { + // iOS 26液态玻璃下,转场动画可以被打断 + } else { + QMUILogWarn(@"UINavigationController (QMUI)", @"popToViewController 时上一次的转场尚未完成,系统会忽略本次 pop,等上一次转场完成后再重新执行 pop, currentViewControllers = %@, viewController = %@", selfObject.viewControllers, viewController); + } } BOOL willPopActually = selfObject.viewControllers.count > 1 && [selfObject.viewControllers containsObject:viewController] && selfObject.topViewController != viewController && action == QMUINavigationActionUnknow;// 系统文档里说 rootViewController 是不能被 pop 的,当只剩下 rootViewController 时当前方法什么事都不会做 @@ -368,7 +376,11 @@ + (void)load { QMUINavigationAction action = selfObject.qmui_navigationAction; if (action != QMUINavigationActionUnknow) { - QMUILogWarn(@"UINavigationController (QMUI)", @"popToRootViewController 时上一次的转场尚未完成,系统会忽略本次 pop,等上一次转场完成后再重新执行 pop, viewControllers = %@", selfObject.viewControllers); + if (QMUIHelper.isUsedLiquidGlass) { + // iOS 26液态玻璃下,转场动画可以被打断 + } else { + QMUILogWarn(@"UINavigationController (QMUI)", @"popToRootViewController 时上一次的转场尚未完成,系统会忽略本次 pop,等上一次转场完成后再重新执行 pop, viewControllers = %@", selfObject.viewControllers); + } } BOOL willPopActually = selfObject.viewControllers.count > 1 && action == QMUINavigationActionUnknow;