QMUI_iOS icon indicating copy to clipboard operation
QMUI_iOS copied to clipboard

QMUITheme 切换时无法刷新 UITabBar/UINavigationBar 的 barStyle、backgroundImage 等样式

Open codingiran opened this issue 5 years ago • 2 comments

考虑下面这种页面结构: tabbardarkmode

一旦在 visibleViewControlle 触发 dark mode 之后,QMUIConfiguration如下属性都可能改变:

@property(nonatomic, strong, nullable) UIImage  *tabBarBackgroundImage;
@property(nonatomic, strong, nullable) UIColor  *tabBarBarTintColor;
@property(nonatomic, strong, nullable) UIColor  *tabBarShadowImageColor;
@property(nonatomic, assign) UIBarStyle         tabBarStyle;
@property(nonatomic, strong, nullable) UIFont   *tabBarItemTitleFont;
@property(nonatomic, strong, nullable) UIColor  *tabBarItemTitleColor;
@property(nonatomic, strong, nullable) UIColor  *tabBarItemTitleColorSelected;
@property(nonatomic, strong, nullable) UIColor  *tabBarItemImageColor;
@property(nonatomic, strong, nullable) UIColor  *tabBarItemImageColorSelected;

tabBarStyle 举例:

- (void)setTabBarStyle:(UIBarStyle)tabBarStyle {
    _tabBarStyle = tabBarStyle;
#ifdef IOS13_SDK_ALLOWED
    if (@available(iOS 13.0, *)) {
        self.tabBarAppearance.backgroundEffect = [UIBlurEffect effectWithStyle:tabBarStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark];
        [self updateTabBarAppearance];
    } else {
#endif
        [UITabBar appearance].barStyle = tabBarStyle;
        [self.appearanceUpdatingTabBarControllers enumerateObjectsUsingBlock:^(UITabBarController * _Nonnull tabBarController, NSUInteger idx, BOOL * _Nonnull stop) {
            tabBarController.tabBar.barStyle = tabBarStyle;
        }];
#ifdef IOS13_SDK_ALLOWED
    }
#endif
}

此时的self.appearanceUpdatingTabBarControllers 是空的,因为如下方式是拿不到我要的UITabbarController的:

- (NSArray <UIViewController *>*)qmui_existingViewControllersOfClass:(Class)class {
    NSMutableSet *viewControllers = [NSMutableSet set];
    ...
    if ([self isKindOfClass:UINavigationController.class]) {
        [viewControllers addObjectsFromArray:[((UINavigationController *)self).visibleViewController qmui_existingViewControllersOfClass:class]];
    }
    if ([self isKindOfClass:UITabBarController.class]) {
        [viewControllers addObjectsFromArray:[((UITabBarController *)self).selectedViewController qmui_existingViewControllersOfClass:class]];
    }
    ....
    return viewControllers.allObjects;
}

因此只能在VC1 内通过qmui_themeDidChangeByManager进行手动修改

- (void)qmui_themeDidChangeByManager:(QMUIThemeManager *)manager identifier:(__kindof NSObject<NSCopying> *)identifier theme:(__kindof NSObject *)theme
{
    [super qmui_themeDidChangeByManager:manager identifier:identifier theme:theme];
    
    ....
    
    //  需要手动刷新样式
    UIImage *tabBarBackgroundImage = [identifier isEqualToString:WEThemeIdentifierDark] ? [[UIImage qmui_imageWithColor:UIColorMake(249, 249, 249)] resizableImageWithCapInsets:UIEdgeInsetsMake(1, 1, 1, 1)] : nil;
    UIColor *tabBarBarTintColor = nil;
    UIColor *tabBarShadowImageColor = UIColorSeparator;
    UIBarStyle tabBarStyle = [identifier isEqualToString:WEThemeIdentifierDark] ? UIBarStyleBlack : UIBarStyleDefault;
    UIFont *tabBarItemTitleFont = nil;
    UIColor *tabBarItemTitleColor = UIColorGray6;
    UIColor *tabBarItemTitleColorSelected = DefaultThemeColor;
    UIColor *tabBarItemImageColor = UIColorGray6;
    UIColor *tabBarItemImageColorSelected = DefaultThemeColor;
    
    self.tabBar.backgroundImage = tabBarBackgroundImage;
    if (@available(iOS 13.0, *)) {
        UITabBarAppearance *tabBarAppearance = [[UITabBarAppearance alloc] init];
        [tabBarAppearance configureWithDefaultBackground];
        tabBarAppearance.backgroundColor = tabBarBarTintColor;
        tabBarAppearance.shadowColor = tabBarShadowImageColor;
        tabBarAppearance.backgroundEffect = [UIBlurEffect effectWithStyle:tabBarStyle == UIBarStyleDefault ? UIBlurEffectStyleSystemChromeMaterialLight : UIBlurEffectStyleSystemChromeMaterialDark];
        [tabBarAppearance qmui_applyItemAppearanceWithBlock:^(UITabBarItemAppearance * _Nonnull itemAppearance) {
            NSMutableDictionary<NSAttributedStringKey, id> *attributes = itemAppearance.normal.titleTextAttributes.mutableCopy;
            attributes[NSFontAttributeName] = tabBarItemTitleFont;
            attributes[NSForegroundColorAttributeName] = tabBarItemTitleColor;
            itemAppearance.normal.titleTextAttributes = attributes.copy;
            attributes[NSForegroundColorAttributeName] = tabBarItemTitleColorSelected;
            itemAppearance.selected.titleTextAttributes = attributes.copy;
            itemAppearance.normal.iconColor = tabBarItemImageColor;
            itemAppearance.selected.iconColor = tabBarItemImageColorSelected;
        }];
        self.tabBar.standardAppearance = tabBarAppearance;
    } else {
        UITabBar.appearance.backgroundImage = tabBarBackgroundImage;
        UITabBar.appearance.barTintColor = tabBarBarTintColor;
        UITabBar.appearance.shadowImage = [UIImage qmui_imageWithColor:tabBarShadowImageColor size:CGSizeMake(1, PixelOne) cornerRadius:0];
        UITabBar.appearance.barStyle = tabBarStyle;
        NSMutableDictionary<NSString *, id> *textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[[UITabBarItem appearance] titleTextAttributesForState:UIControlStateNormal]];
        if (tabBarItemTitleFont) {
            textAttributes[NSFontAttributeName] = tabBarItemTitleFont;
        }
        if (tabBarItemTitleColor) {
            textAttributes[NSForegroundColorAttributeName] = tabBarItemTitleColor;
        }
        NSMutableDictionary<NSString *, id> *selected_textAttributes = [[NSMutableDictionary alloc] initWithDictionary:[[UITabBarItem appearance] titleTextAttributesForState:UIControlStateSelected]];
        if (tabBarItemTitleColorSelected) {
            selected_textAttributes[NSForegroundColorAttributeName] = tabBarItemTitleColorSelected;
        }
        [UITabBarItem.appearance setTitleTextAttributes:textAttributes forState:UIControlStateNormal];
        [UITabBarItem.appearance setTitleTextAttributes:selected_textAttributes forState:UIControlStateSelected];
        UITabBar.appearance.tintColor = tabBarItemImageColorSelected;
        self.tabBar.barStyle = tabBarStyle;
        self.tabBar.barTintColor = tabBarBarTintColor;
        self.tabBar.shadowImage = [UIImage qmui_imageWithColor:tabBarShadowImageColor size:CGSizeMake(1, PixelOne) cornerRadius:0];
        [self.tabBar.items enumerateObjectsUsingBlock:^(UITabBarItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [obj setTitleTextAttributes:textAttributes forState:UIControlStateNormal];
            [obj setTitleTextAttributes:selected_textAttributes forState:UIControlStateSelected];
            [obj qmui_updateTintColorForiOS12AndEarlier:tabBarItemImageColor];
        }];
        self.tabBar.tintColor = tabBarItemImageColorSelected;
    }
}

需要手动修改的东西非常多,也都是QMUIConfiguration里复制出来的重复代码...能否将QMUIConfiguration内对于 UITabbar修改的代码开放出来,比如抽一个方法到头文件,方便一些特殊页面结构快速修改

codingiran avatar Dec 30 '19 08:12 codingiran

+1

jiasongs avatar Jan 04 '20 09:01 jiasongs

QMUITheme 发生变化时,它内置两种方式去更新样式:

  1. 某些对象的某些属性是支持动态的,例如 UIColorUIImage 类型,这种情况会在 - [UIView(QMUITheme) qmui_themeDidChangeByManager:identifier:theme:] 里按照 QMUIThemePropertiesRegister 提供的属性列表依次调用 setter,以触发外观的刷新。这种方式只支持属性,不支持多参数的方法(例如导航栏的背景图是通过 - [UINavigationBar setBackgroundImage:forBarMetrics:] 设置的,这个方法有两个参数,不支持这种方式,所以导航栏的背景图无法通过方法 1 来刷新),也不支持非对象的类型(例如 UIBarStyle)。
  2. 如果有使用配置表,则在主题发生变化时,配置表也会重新加载,在 QMUIConfiguration 的某些属性的 setter (例如 issue 提到的 setTabBarStyle:)里会获取界面中的特定 View 去主动刷新,但如果这里寻找特定 View 的方式没有命中当前的业务场景,找不到 View,就无法被刷新。

回到 issue 描述的场景里,其实不需要这种特殊的 viewController 结构,只需要将一个普通的 UINavigationBarUITabBar 直接添加到当前界面,然后触发主题切换,就会发现这些 bar 的 backgroundImagebarStyle 无法被更新——因为这些属性不符合上述的两点,也即非单一属性,或者非对象数据类型,也不匹配配置表里寻找 View 的逻辑。

这应该是 QMUITheme 一个通用的 bug,我们再考虑如何解决,在此之前确实只能按你说的,用很麻烦的方式从配置表里重新读取值再设置上去。

MoLice avatar Jan 06 '20 08:01 MoLice