JXTheme是一个提供主题属性配置的轻量级基础库。
- 支持iOS 9+,让你的APP更早的实现
DarkMode
; - 使用
theme
命名空间属性:view.theme.xx = xx
。告别theme_xx
属性扩展用法; - 使用
ThemeProvider
传入闭包配置。根据不同的ThemeStyle
完成主题属性配置,实现最大化的自定义; -
ThemeStyle
可通过extension
自定义style,不再局限于light
和dark
; - 提供
customization
属性,作为主题切换的回调入口,可以灵活配置任何属性。不再局限于提供的backgroundColor
、textColor
等属性; - 支持控件设置
overrideThemeStyle
,会影响到其子视图; - 提供根据
ThemeStyle
配置属性的常规封装、Plist文件静态加载、服务器动态加载示例;
- iOS 9.0+
- XCode 10.2.1+
- Swift 5.0+
Clone代码,把Sources文件夹拖入项目,就可以使用了;
target '<Your Target Name>' do
pod 'JXTheme'
end
先执行pod repo update
,再执行pod install
在cartfile文件添加:
github "pujiaxin33/JXTheme"
然后执行carthage update --platform iOS
,其他配置请参考Carthage文档
ThemeStyle
内部仅提供了一个默认的unspecified
style,其他的业务style需要自己添加,比如只支持light
和dark
,代码如下:
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")!
}
})
如果库没有原生支持某个属性,可以在customization里面统一处理。
view.theme.customization = ThemeProvider({[weak self] style in
//可以选择任一其他属性
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)
}
})
如果某一个属性在项目中经常使用,使用上面的自定义属性配置觉得麻烦,就可以自己extension ThemeWrapper添加想要的属性。(ps:你也可以提交一个Pull Request申请添加哟)
下面是UILabel添加shadowColor的示例:
//自定义添加ThemeProperty,目前仅支持UIView、CALayer、UIBarItem及其它们的子类
extension ThemeWrapper where Base: UILabel {
var shadowColor: ThemeProvider<UIColor>? {
set(new) {
let baseItem = self.base
ThemeTool.setupViewThemeProperty(view: self.base, key: "UILabel.shadowColor", provider: new) {[weak baseItem] (style) in
baseItem?.shadowColor = new?.provider(style)
}
}
get { return ThemeTool.getThemeProvider(target: self.base, with: "UILabel.shadowColor") as? ThemeProvider<UIColor> }
}
}
调用还是一样的:
//自定义属性shadowColor
shadowColorLabel.shadowOffset = CGSize(width: 0, height: 2)
shadowColorLabel.theme.shadowColor = ThemeProvider({ style in
if style == .dark {
return .red
}else {
return .green
}
})
JXTheme
是一个提供主题属性配置的轻量级基础库,不限制使用哪种方式加载资源。下面提供的三个示例仅供参考。
比如在项目中添加如下代码:
extension ThemeProvider {
//根据项目支持的ThemeStyle调整
init(light: T, dark: T) {
self.init { style in
switch style {
case .light: return light
case .dark: return dark
default: return light
}
}
}
}
在业务代码中调用:
tableView.theme.backgroundColor = ThemeProvider(light: UIColor.white, dark: UIColor.white)
这样就可以避免ThemeProvider闭包的形式,更加简洁。
一般的换肤需求,都会有一个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文件加载配置的具体值,具体代码参加Example
的StaticSourceManager
类
与常规配置封装一样,只是该方法是从服务器加载配置的具体值,具体代码参加Example
的DynamicSourceManager
类
某些业务需求会存在一个控件有多种状态,比如选中与未选中。不同的状态对于不同的主题又会有不同的配置。配置代码参考如下:
statusLabel.theme.textColor = ThemeProvider({[weak self] (style) in
if self?.statusLabelStatus == .isSelected {
//选中状态一种配置
if style == .dark {
return .red
}else {
return .green
}
}else {
//未选中状态另一种配置
if style == .dark {
return .white
}else {
return .black
}
}
})
当控件的状态更新时,需要刷新当前的主题属性配置,代码如下:
func statusDidChange() {
statusLabel.theme.textColor?.refresh()
}
如果你的控件支持多个状态属性,比如有textColor
、backgroundColor
、font
等等,你可以不用一个一个的主题属性调用refresh
方法,可以使用下面的代码完成所有配置的主题属性刷新:
func statusDidChange() {
statusLabel.theme.refresh()
}
不管主题如何切换,overrideThemeStyleParentView
及其子视图的themeStyle
都是dark
overrideThemeStyleParentView.theme.overrideThemeStyle = .dark
- 如果你给系统的类扩展了N个函数,当你在使用该类时,进行函数索引时,就会有N个扩展的方法干扰你的选择。尤其是你在进行其他业务开发,而不是想配置主题属性时。
- 像
Kingfisher
、SnapKit
等知名三方库,都使用了命名空间属性实现对系统类的扩展,这是一个更Swift
的写法,值得学习。
extension Notification.Name {
public static let JXThemeDidChange = Notification.Name("com.jiaxin.theme.themeDidChangeNotification")
}
/// 配置存储的标志key。可以设置为用户的ID,这样在同一个手机,可以分别记录不同用户的配置。需要优先设置该属性再设置其他值。
public var storeConfigsIdentifierKey: String = "default"
当你的应用最低支持iOS13时,如果需要的话可以按照如下指南,迁移到系统方案。 迁移到系统API指南,点击阅读
这里的属性是有继承关系的,比如UIView
支持backgroundColor
属性,那么它的子类UILabel
等也就支持backgroundColor
。如果没有你想要支持的类或属性,欢迎提PullRequest进行扩展。
backgroundColor
tintColor
alpha
customization
font
textColor
shadowColor
highlightedTextColor
attributedText
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)
font
textColor
attributedText
attributedPlaceholder
keyboardAppearance
font
textColor
attributedText
keyboardAppearance
image
backgroundColor
borderColor
borderWidth
shadowColor
customization
fillColor
strokeColor
barStyle
barTintColor
titleTextAttributes
largeTitleTextAttributes
barStyle
barTintColor
shadowImage
barStyle
barTintColor
keyboardAppearance
barStyle
barTintColor
onTintColor
thumbTintColor
thumbTintColor
minimumTrackTintColor
maximumTrackTintColor
minimumValueImage
maximumValueImage
attributedTitle
progressTintColor
trackTintColor
progressImage
trackImage
pageIndicatorTintColor
currentPageIndicatorTintColor
func setTitleTextAttributes(_ attributesProvider: ThemeAttributesDynamicProvider?, for state: UIControl.State)
image
tintColor
style
color
indicatorStyle
separatorColor
sectionIndexColor
sectionIndexBackgroundColor
有任何疑问或建议,欢迎提Issue和Pull Request进行交流🤝