JXTheme 0.0.7

JXTheme 0.0.7

pujiaxin 维护。



JXTheme 0.0.7

  • pujiaxin33

中文文档

JXTheme 是一个轻量级的主题属性配置库。为了实现主题切换,主要解决了以下五个问题

1. 如何优雅地设置主题属性

通过将命名空间属性 theme 扩展到控件,类似于 SnapKit 中的 snpKingfisher 中的 kf,可以将支持主题修改的属性集中到 theme 属性。这比直接将属性 'theme_backgroundColor' 扩展到控件上更优雅。核心代码如下

view.theme.backgroundColor = ThemeProvider({ (style) in
    If style == .dark {
        Return .white
    }else {
        Return .black
    }
})

2. 如何根据传入的样式配置相应的值

参考 iOS13 系统API UIColor(dynamicProvider: <UITraitCollection) -> UIColor>)。自定义 ThemeProvider 结构,初始化器是 init(_ provider: @escaping ThemePropertyProvider<T>)。传递的参数 ThemePropertyProvider 是定义为 typealias ThemePropertyProvider<T> = (ThemeStyle) -> T 的闭包。这允许对不同的控件和不同的属性配置实现最大程度的自定义。核心代码参考第一步的示例代码。

3. 如何保存主题属性的配置闭包

添加到控件中的 Associated object 属性 providers 以存储 ThemeProvider。核心代码如下

Public extension ThemeWrapper where Base: UIView {
    Var backgroundColor: ThemeProvider<UIColor>? {
        Set(new) {
            If new != nil {
                Let baseItem = self.base
                Let config: ThemeCustomizationClosure = {[weak baseItem] (style) in
                    baseItem?.backgroundColor = new?.provider(style)
                }
                / / Stored in the extended properties provider
                Var newProvider = new
                newProvider?.config = config
                Self.base.providers["UIView.backgroundColor"] = newProvider
                ThemeManager.shared.addTrackedObject(self.base, addedConfig: config)
            }else {
                self.base.configs.removeValue(forKey: "UIView.backgroundColor")
            }
        }
        Get { return self.base.providers["UIView.backgroundColor"] as? ThemeProvider<UIColor> }
    }
}

4. 如何跟踪支持主题属性的控件

为了切换主题,需要通知支持主题属性配置的控件。通过在设置主题属性时跟踪目标控件。核心代码是第3步中的代码。

ThemeManager.shared.addTrackedObject(self.base, addedConfig: config)

5. 如何切换主题并调用主题属性关闭函数

通过调用 ThemeManager.changeTheme(to: style) 来切换主题,然后方法内部在跟踪控件的 providers 中调用 ThemeProvider.provider 的主题属性来配置闭包。核心代码如下。

Public func changeTheme(to style: ThemeStyle) {
    currentThemeStyle = style
    self.trackedHashTable.allObjects.forEach { (object) in
        If let view = object as? UIView {
            view.providers.values.forEach { self.resolveProvider($0) }
        }
    }
}
Private func resolveProvider(_ object: Any) {
    //castdown generic
    If let provider = object as? ThemeProvider<UIColor> {
        Provider.config?(currentThemeStyle)
    }else ...
}

特性

  • 支持 iOS 9+,让您的应用更早实现 DarkMode
  • 使用 theme 命名空间属性:view.theme.xx = xx。告别 theme_xx 属性扩展的使用;
  • ThemeStyle 可以通过 extension 自定义,不再局限于 lightdark
  • 提供 customization 属性作为主题切换的回调入口,具有配置任何属性的灵活性。不再局限于提供的属性,如 backgroundColortextColor
  • 支持控件设置 overrideThemeStyle,影响其子视图;

预览

preview

要求

  • iOS 9.0+
  • XCode 10.2.1+
  • Swift 5.0+

安装

手册

克隆代码,将“Sources”文件夹拖入项目,即可使用;

CocoaPods

Target '<Your Target Name>' do
     Pod 'JXTheme'
End

首先执行 pod repo update,然后执行 pod install

Carthage

在 cartfile 中添加

Github "pujiaxin33/JXTheme"

然后执行 carthage update --platform iOS。对于其他配置,请参阅 Carthage 文档。

#用法

通过扩展 ThemeStyle 添加自定义样式

ThemeStyle 仅提供默认的 unspecified 样式。其他业务样式需要自行添加。例如,只支持 lightdark。代码如下

Extension ThemeStyle {
    Static let light = ThemeStyle(rawValue: "light")
    Static let dark = ThemeStyle(rawValue: "dark")
}

基本使用

view.theme.backgroundColor = ThemeProvider({ (style) in
    If style == .dark {
        Return .white
    }else {
        Return .black
    }
})
imageView.theme.image = ThemeProvider({ (style) in
    If style == .dark {
        Return UIImage(named: "catWhite")!
    }else {
        Return UIImage(named: "catBlack")!
    }
})

自定义属性配置

View.theme.customization = ThemeProvider({[weak self] style in
    / / You can choose any other property
    If style == .dark {
        Self?.view.bounds = CGRect(x: 0, y: 0, width: 30, height: 30)
    }else {
        Self?.view.bounds = CGRect(x: 0, y: 0, width: 80, height: 80)
    }
})

配置示例

JXTheme是一个轻量级的基库,提供了主题属性的配置,不限制资源加载的方式。下方的三个示例仅供参考。

通用配置包示例

存在一种针对通用皮肤需要UI标准。例如,UILabel.textColor定义了三个级别,代码如下

Enum TextColorLevel: String {
    Case normal
    Case mainTitle
    Case subTitle
}

然后你可以封装一个全局函数,传递TextColorLevel到返回相应的配置闭包,这可以大大减少配置过程中的代码量。全局函数如下

Func dynamicTextColor(_ level: TextColorLevel) -> ThemeProvider<UIColor> {
    Switch level {
    Case .normal:
        Return ThemeProvider({ (style) in
            If style == .dark {
                Return UIColor.white
            }else {
                Return UIColor.gray
            }
        })
    Case .mainTitle:
        ...
    Case .subTitle:
        ...
    }
}

配置主题属性的代码如下

themeLabel.theme.textColor = dynamicTextColor(.mainTitle)

本地Plist文件配置示例

通用配置包相同,区别在于该方法从本地Plist文件中加载配置值。具体代码参考Example``StaticSourceManager类。

根据服务器动态添加主题

通用配置包相同,区别在于该方法从服务器加载配置的具体值。具体代码参考Example类的DynamicSourceManager类。

具有多个状态的控件

某些业务需求存在对具有多个状态(如已选中/未选中)的控件。不同状态对应不同主题的配置。配置代码如下

statusLabel.theme.textColor = ThemeProvider({[weak self] (style) in
    If self?.statusLabelStatus == .isSelected {
        / / selected state a configuration
        If style == .dark {
            Return .red
        }else {
            Return .green
        }
    }else {
        //Unselected another configuration
        If style == .dark {
            Return .white
        }else {
            Return .black
        }
    }
})

当控件的州被更新时,你需要刷新当前主题属性配置,代码如下

Func statusDidChange() {
    statusLabel.theme.textColor?.refresh()
}

如果你的控件支持多个状态属性,如 textColorbackgroundColorfont 等,你可以调用 refresh 方法而不使用主题属性之一。你可以使用以下代码来完成所有配置的主题。属性刷新

Func statusDidChange() {
    statusLabel.theme.refresh()
}

重写主题样式

无论主题如何切换,overrideThemeStyleParentView及其子视图的themeStyle都是dark模式

overrideThemeStyleParentView.theme.overrideThemeStyle = .dark

其他技巧

为什么使用主题作用域属性而不是 themed_xx 扩展属性?

  • 当你将 N 个函数扩展到系统类中,当你使用这个类时,会有 N 个扩展方法干扰你的选择。特别是当你做其他业务开发时,而不仅仅是当你想配置主题属性。
  • KingfisherSnapKit 这样知名的第三方库,都使用名称空间属性来实现对系统类的扩展。这是一种更 Swift 表达方式,值得学习。

主题切换通知

Extension Notification.Name {
    Public static let JXThemeDidChange = Notification.Name("com.jiaxin.theme.themeDidChangeNotification")
}

ThemeManager 根据用户ID存储主题配置

/// Configure the stored flag key. Can be set to the user's ID, so that in the same phone, you can record the configuration of different users. You need to set this property first and then set other values.
Public var storeConfigsIdentifierKey: String = "default"

迁移到系统API指南

当您的应用至少支持iOS13时,如果需要遵循以下指南,您可以迁移到系统计划。[迁移到系统API指南,点击阅读] (https://github.com/pujiaxin33/JXTheme/blob/master/Document/%E8%BF%81%E7%A7%BB%E5%88%B0%E7% B3%BA%E7%BB%9FAPI%E6%8C%87%E5%8D%97.md)

当前支持的类及其属性

这里的属性是继承的。例如,UIView 支持的 backgroundColor 属性,那么其子类 UILabel 也支持 backgroundColor。如果您没有想要支持的类或属性,欢迎您扩展PullRequest。

UIView

  • backgroundColor
  • tintColor
  • alpha
  • 定制

UILabel

  • font
  • textColor
  • shadowColor
  • highlightedTextColor
  • attributedText

UIButton

  • func setTitleColor(_ colorProvider: ThemeColorDynamicProvider?, for state: UIControl.State)
  • func setTitleShadowColor(_ colorProvider: ThemeColorDynamicProvider?, for state: UIControl.State)
  • func setAttributedTitle(_ textProvider: ThemeAttributedTextDynamicProvider?, for state: UIControl.State)
  • func setImage(_ imageProvider: ThemeImageDynamicProvider?, for state: UIControl.State)
  • func setBackgroundImage(_ imageProvider: ThemeImageDynamicProvider?, for state: UIControl.State)

UITextField

  • font
  • textColor
  • attributedText
  • attributedPlaceholder
  • keyboardAppearance

UITextView

  • font
  • textColor
  • attributedText
  • keyboardAppearance

UIImageView

  • image

CALayer

  • backgroundColor
  • borderColor
  • borderWidth
  • shadowColor
  • 定制

CAShapeLayer

  • fillColor
  • strokeColor

UINavigationBar

  • barStyle
  • barTintColor
  • titleTextAttributes
  • largeTitleTextAttributes

UITabBar

  • barStyle
  • barTintColor

UISearchBar

  • barStyle
  • barTintColor
  • keyboardAppearance

UIToolbar

  • barStyle
  • barTintColor

UISwitch

  • onTintColor
  • thumbTintColor

UISlider

  • thumbTintColor
  • minimumTrackTintColor
  • maximumTrackTintColor
  • minimumValueImage
  • maximumValueImage

UIRefreshControl

  • attributedTitle

UIProgressView

  • progressTintColor
  • trackTintColor
  • progressImage
  • trackImage

UIPageControl

  • pageIndicatorTintColor
  • currentPageIndicatorTintColor

UIBarItem

  • func setTitleTextAttributes(_ attributesProvider: 主题属性动态提供者?, for state: UIControl.状态)

UIBarButtonItem

  • tintColor

UIActivityIndicatorView

  • 样式

UIScrollView

  • 指示器样式

UITableView

  • 分割线颜色
  • 分区索引颜色
  • 分区索引背景颜色

Contribution

如果您有任何问题或建议,请随时通过问题和拉取请求与我们联系🤝