前言 - 示例 - 安装 - 参考 - 常见问题 - 贡献 - English Document
运行:打开
SwiftTheme.xcworkspace
选择构建目标为PlistDemo
项目需求,我们要为 App 开发夜间模式功能。我们的需求不是简单的调整亮度或者alpha
,而是更换为一套更深色的UI。因此所谓夜间模式其实就是特定的更换主题(换肤)功能。
如何实现呢?判断某个全局变量,然后在初始化视图控件时设置不同的背景色或者加载不同的切图文件?但是在切换主题时,已经初始化好的视图控件呢?没错,也许你也想到了通过通知让相应的视图控件修改背景色或切图。想到这里你应该也意识到了Controller中将充斥着注册通知、if...else
、更新视图控件的代码,糟糕的是如果忘记了注销通知还可能引起应用崩溃。
一番思考后,我们对该任务提出了更高的要求,打造一套简单、可复用的主题框架,正如你看到的这样。
将SwiftTheme
打造为一款简单、功能丰富、高性能、可扩展的主题框架(换肤框架),为 iOS 平台提供一个统一的主题解决方案。
让 UIView
随主题变换背景色:
view.theme_backgroundColor = ["#FFF", "#000"]
让 UILabel
和 UIButton
随主题变换文字颜色:
label.theme_textColor = ["#000", "#FFF"]
button.theme_setTitleColor(["#000", "#FFF"], forState: .Normal)
让 UIImageView
随主题变换切图:
imageView.theme_image = ["day", "night"]
// 不想通过切图名,想通过 UIImage 来设置不同主题的图片也是可以的
imageView.theme_image = ThemeImagePicker(images: image1, image2)
然后,当你执行如下代码时,奇迹发生了!
// 例如isNight为true,imageView将会使用 "night" 的切图
ThemeManager.setTheme(index: isNight ? 1 : 0)
随时获取当前主题的索引:
ThemeManager.currentThemeIndex // Readonly
直接根据索引切换主题,便于快速开发。适合主题不多、无需下载主题的App。
关于字面量需要注意的:
// 以下的写法是错误的
let colors = ["#FFF", "#000"]
view.theme_backgroundColor = colors
// 应该这样
view.theme_backgroundColor = ["#FFF", "#000"]
// 或者
let colorPickers: ThemeColorPicker = ["#FFF", "#000"]
view.theme_backgroundColor = colorPickers
因为 theme_backgroundColor 接受的是ThemeColorPicker 类型的参数,而不是Array,而 view.theme_backgroundColor = ["#FFF", "#000"] 其实是用字面量初始化一个ThemeColorPicker 并赋值给theme_backgroundColor
为了满足通过网络下载和安装主题包的需求,我们支持以plist
配置主题。简单讲就是在plist
中记录配置参数,比如背景色、切图文件名等,在代码中通过keyPath
来指定相应的位置。因此,该plist
文件以及用到的资源文件就组成了一个主题包。
以下为用法示例:
view.theme_backgroundColor = "Global.backgroundColor"
imageView.theme_image = "SelectedThemeCell.iconImage"
与索引方式类似,只是具体的参数值变为了
plist
中的keyPath
,正因如此赋予了它扩展的能力。
切换主题时参数为plist
名称,这里以bundle
中的plist
文件及资源文件为例,使用沙箱中的文件也是可以的。
ThemeManager.setTheme(plistName: "Red", path: .MainBundle)
plist
方式增加主题无需修改代码,可以无限扩展主题,因此你完全可以通过这种方式为你的用户开发下载安装主题的功能。
上面用到的plist
、image
展示如下:
如果你想在切换主题时执行自定义任务,或者当SwiftTheme
无法满足你的需求时,可以注册名为ThemeUpdateNotification
的通知,你可以在任何地方观察这个通知,来实现自定义的行为:
NSNotificationCenter.default.addObserver(
self,
selector: #selector(doSomethingMethod),
name: NSNotification.Name(rawValue: ThemeUpdateNotification),
object: nil
)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomething) name:@"ThemeUpdateNotification" object:nil];
完全兼容Objective-C,用法示例:
lbl.theme_backgroundColor = [ThemeColorPicker pickerWithColors:@[@"#FAF9F9", @"#E2E2E2"]];
- 纯Swift编写
- 兼容Objective-C
- 基于
runtime
- 易于集成
- 扩展属性以
theme_*
开头,便于 IDE 自动补全 - 支持
UIAppearance
- 自动监听主题切换,更新UI
- 支持通过字面量设置不同主题,通过索引进行切换
- 支持使用
plist
设置主题,可直接通过项目资源加载,或远程下载至沙盒中加载 - 主题参数配置错误时日志提示
- 强类型
ThemePicker
- 完整的Demo
pod 'SwiftTheme'
use_frameworks!
github "wxxsw/SwiftTheme"
拷贝Source
文件夹下的所有文件到你的项目中
子类会拥有父类的属性,例如
UILabel
也会拥有UIView
的theme_alpha
等属性,这种属性不一一列出
- var theme_alpha: ThemeCGFloatPicker?
- var theme_backgroundColor: ThemeColorPicker?
- var theme_tintColor: ThemeColorPicker?
- func theme_setStatusBarStyle(picker: ThemeStatusBarStylePicker, animated: Bool)
- var theme_tintColor: ThemeColorPicker?
- var theme_font: ThemeFontPicker?
- var theme_textColor: ThemeColorPicker?
- var theme_textAttributes: ThemeStringAttributesPicker?
- var theme_highlightedTextColor: ThemeColorPicker?
- var theme_shadowColor: ThemeColorPicker?
- var theme_barStyle: ThemeBarStylePicker?
- var theme_barTintColor: ThemeColorPicker?
- var theme_titleTextAttributes: ThemeDictionaryPicker?
- var theme_barStyle: ThemeBarStylePicker?
- var theme_barTintColor: ThemeColorPicker?
- var theme_separatorColor: ThemeColorPicker?
- var theme_font: ThemeFontPicker?
- var theme_keyboardAppearance: ThemeKeyboardAppearancePicker?
- var theme_textColor: ThemeColorPicker?
- var theme_placeholderAttributes: ThemeDictionaryPicker?
- var theme_font: ThemeFontPicker?
- var theme_textColor: ThemeColorPicker?
- var theme_barStyle: ThemeBarStylePicker?
- var theme_barTintColor: ThemeColorPicker?
- var theme_selectedSegmentTintColor: ThemeColorPicker?
- func theme_setTitleTextAttributes(_ picker: ThemeStringAttributesPicker?, forState state: UIControl.State)
- var theme_onTintColor: ThemeColorPicker?
- var theme_thumbTintColor: ThemeColorPicker?
- var theme_thumbTintColor: ThemeColorPicker?
- var theme_minimumTrackTintColor: ThemeColorPicker?
- var theme_maximumTrackTintColor: ThemeColorPicker?
- var theme_barStyle: ThemeBarStylePicker?
- var theme_barTintColor: ThemeColorPicker?
- var theme_progressTintColor: ThemeColorPicker?
- var theme_trackTintColor: ThemeColorPicker?
- var theme_pageIndicatorTintColor: ThemeColorPicker?
- var theme_currentPageIndicatorTintColor: ThemeColorPicker?
- var theme_image: ThemeImagePicker?
- var theme_activityIndicatorViewStyle: ThemeActivityIndicatorViewStylePicker?
- func theme_setImage(picker: ThemeImagePicker?, forState state: UIControlState)
- func theme_setBackgroundImage(picker: ThemeImagePicker?, forState state: UIControlState)
- func theme_setTitleColor(picker: ThemeColorPicker?, forState state: UIControlState)
- func theme_setAttributedTitle(picker: ThemeAttributedStringPicker?, forState state: UIControlState)
- var theme_backgroundColor: ThemeCGColorPicker?
- var theme_borderWidth: ThemeCGFloatPicker?
- var theme_borderColor: ThemeCGColorPicker?
- var theme_shadowColor: ThemeCGColorPicker?
- var theme_foregroundColor: ThemeCGColorPicker?
- var theme_colors: ThemeAnyPicker?
- var theme_titleAttributes: ThemeDictionaryPicker?
- var theme_effect: ThemeVisualEffectPicker?
// 目前支持的颜色格式有:
// "#ffcc00" RGB十六进制
// "#ffcc00dd" +alpha
// "#FFF" RGB十六进制缩写
// "#013E" +alpha
①
ThemeColorPicker(colors: "#FFFFFF", "#000")
ThemeColorPicker(colors: UIColor.red, UIColor.blue)
ThemeColorPicker(colors: "#FFFFFF", "#000")
ThemeColorPicker.pickerWithColors(["#FFFFFF", "#000"])
②
ThemeColorPicker(keyPath: "someStringKeyPath")
ThemeColorPicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeImagePicker(names: "image1", "image2")
ThemeImagePicker.pickerWithNames(["image1", "image2"])
ThemeImagePicker(images: UIImage(named: "image1")!, UIImage(named: "image2")!)
ThemeImagePicker.pickerWithImages([UIImage(named: "image1")!, UIImage(named: "image2")!])
②
ThemeImagePicker(keyPath: "someStringKeyPath")
ThemeImagePicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeCGFloatPicker(floats: 1.0, 0.7)
ThemeCGFloatPicker.pickerWithFloats([1.0, 0.7])
②
ThemeCGFloatPicker(keyPath: "someNumberKeyPath")
ThemeCGFloatPicker.pickerWithKeyPath("someNumberKeyPath")
①
ThemeCGColorPicker(colors: "#FFFFFF", "#000")
ThemeCGColorPicker(colors: UIColor.red, UIColor.blue)
ThemeCGColorPicker(colors: UIColor.red.cgColor, UIColor.blue.cgColor)
ThemeCGColorPicker(colors: "#FFFFFF", "#000")
ThemeCGColorPicker.pickerWithColors(["#FFFFFF", "#000"])
②
ThemeCGColorPicker(keyPath: "someStringKeyPath")
ThemeCGColorPicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeFontPicker(fonts: UIFont.systemFont(ofSize: 10), UIFont.systemFont(ofSize: 11))
ThemeFontPicker.pickerWithFonts([UIFont.systemFont(ofSize: 10), UIFont.systemFont(ofSize: 11)])
②
// 格式示例 "PingFangSC-Regular,16"
ThemeFontPicker(keyPath: "someStringKeyPath")
ThemeFontPicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeDictionaryPicker(dicts: ["key": "value"], ["key": "value"])
ThemeDictionaryPicker.pickerWithDicts([["key": "value"], ["key": "value"]])
ThemeDictionaryPicker.pickerWithAttributes([NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)])
②
ThemeBarStylePicker(keyPath: "someStringKeyPath") { (Any?) -> [String: AnyObject]? in ... }
①
ThemeStringAttributesPicker(["key": "value"], ["key": "value"])
ThemeStringAttributesPicker.pickerWithAttributes([NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)])
②
ThemeStringAttributesPicker(keyPath: "someStringKeyPath") { (Any?) -> [NSAttributedString.Key: Any]? in ... }
①
ThemeAttributedStringPicker(NSAttributedString(...), NSAttributedString(...))
ThemeAttributedStringPicker.pickerWithAttributedStrings([NSAttributedString(...)])
②
ThemeAttributedStringPicker(keyPath: "someStringKeyPath") { (Any?) -> NSAttributedString? in ... }
①
ThemeBarStylePicker(styles: .default, .black)
ThemeBarStylePicker.pickerWithStyles([.default, .black])
ThemeBarStylePicker.pickerWithStringStyles(["default", "black"])
②
// 在自定的`Key`中设置指定的`Value`,匹配字符串即可生效
// 可选的值有:"default" 和 "black"
ThemeBarStylePicker(keyPath: "someStringKeyPath")
ThemeBarStylePicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeStatusBarStylePicker(styles: .default, .lightContent)
ThemeStatusBarStylePicker.pickerWithStyles([.default, .lightContent])
ThemeStatusBarStylePicker.pickerWithStringStyles(["default", "lightContent"])
②
// 在自定的`Key`中设置指定的`Value`,匹配字符串即可生效
// 可选的值有:"default" 和 "lightContent"
ThemeStatusBarStylePicker(keyPath: "someStringKeyPath")
ThemeStatusBarStylePicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeKeyboardAppearancePicker(styles: .default, .dark, .light)
ThemeKeyboardAppearancePicker.pickerWithStyles([.default, .dark, .light])
ThemeKeyboardAppearancePicker.pickerWithStringStyles(["default", "dark", "light"])
②
// 在自定的`Key`中设置指定的`Value`,匹配字符串即可生效
// 可选的值有:"default"、"dark" 和 "light"
ThemeKeyboardAppearancePicker(keyPath: "someStringKeyPath")
ThemeKeyboardAppearancePicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeActivityIndicatorViewStylePicker(styles: .whiteLarge, .white, .gray)
ThemeActivityIndicatorViewStylePicker.pickerWithStyles([.whiteLarge, .white, .gray])
ThemeActivityIndicatorViewStylePicker.pickerWithStringStyles(["whiteLarge", "white", "gray"])
②
// 在自定的`Key`中设置指定的`Value`,匹配字符串即可生效
// 可选的值有:"whiteLarge"、"white" 和 "gray"
ThemeActivityIndicatorViewStylePicker(keyPath: "someStringKeyPath")
ThemeActivityIndicatorViewStylePicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeVisualEffectPicker(effects: UIBlurEffect(style: .light), UIBlurEffect(style: .dark))
ThemeVisualEffectPicker.pickerWithEffects([UIBlurEffect(style: .light), UIBlurEffect(style: .dark)])
ThemeVisualEffectPicker.pickerWithStringEffects(["light", "dark", "extralight", "prominent", "regular"])
②
// 在自定的`Key`中设置指定的`Value`,匹配字符串即可生效
// 可选的值有:"light"、"dark"、"prominent" 和 "regular"
ThemeVisualEffectPicker(keyPath: "someStringKeyPath")
ThemeVisualEffectPicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeAnyPicker(anys: 0, "123", UIColor.red)
ThemeAnyPicker.pickerWithAnys([0, "123", UIColor.red])
②
ThemeAnyPicker(keyPath: "someStringKeyPath")
ThemeAnyPicker.pickerWithKeyPath("someStringKeyPath")
下载SwiftTheme
项目,了解如何在项目中使用,其中包含四个Demo Target:
Demo
演示了如何使用索引进行管理,退出时保存上次使用的主题等常见需求PlistDemo
演示了如何使用plist
进行管理,并包含下载保存主题Zip包等功能JsonDemo
类似PlistDemo
, 但是使用json
.OCDemo
是Demo
的 Objective-C 版本.TVOSDemo
用来测试 tvOS 的兼容性.
-
使用theme_setStatusBarStyle设置状态栏样式时没有任何效果,为什么?
答:你需要将
Info.plist
中的View Controller-based status bar appearence
设置为NO
。 -
我可以手动取消某个属性的主题吗?
答:可以,传入
nil
即可,例如view.theme_backgroundColor = nil
。
如果你需要帮助或者遇到Bug,请创建一个Issue
期待你的贡献 :D。但是请确保你添加的功能不要偏离主旨,并保证其足够简单易用,如果你不确定可以先通过issue与我们讨论。
The MIT License (MIT)