SwiftTheme

一款简单、功能丰富、高性能、可扩展的主题框架(换肤框架),为 iOS 平台提供一个统一的主题解决方案。

SwiftTheme地址:https://github.com/wxxsw/SwiftTheme

前言 - 示例 - 安装 - 参考 - 常见问题 - 贡献 - English Document

截屏

运行:打开SwiftTheme.xcworkspace选择构建目标为PlistDemo

前言

缘起

项目需求,我们要为 App 开发夜间模式功能。我们的需求不是简单的调整亮度或者alpha,而是更换为一套更深色的UI。因此所谓夜间模式其实就是特定的更换主题(换肤)功能。

如何实现呢?判断某个全局变量,然后在初始化视图控件时设置不同的背景色或者加载不同的切图文件?但是在切换主题时,已经初始化好的视图控件呢?没错,也许你也想到了通过通知让相应的视图控件修改背景色或切图。想到这里你应该也意识到了Controller中将充斥着注册通知、if...else、更新视图控件的代码,糟糕的是如果忘记了注销通知还可能引起应用崩溃。

一番思考后,我们对该任务提出了更高的要求,打造一套简单、可复用的主题框架,正如你看到的这样。

目标

SwiftTheme打造为一款简单、功能丰富、高性能、可扩展的主题框架(换肤框架),为 iOS 平台提供一个统一的主题解决方案。

示例

索引方式

UIView 随主题变换背景色:

view.theme_backgroundColor = ["#FFF", "#000"]

UILabelUIButton 随主题变换文字颜色:

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配置主题。简单讲就是在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方式增加主题无需修改代码,可以无限扩展主题,因此你完全可以通过这种方式为你的用户开发下载安装主题的功能。

上面用到的plistimage展示如下:


自定义行为

如果你想在切换主题时执行自定义任务,或者当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

完全兼容Objective-C,用法示例:

lbl.theme_backgroundColor = [ThemeColorPicker pickerWithColors:@[@"#FAF9F9", @"#E2E2E2"]];

主要特点

  • 纯Swift编写
  • 兼容Objective-C
  • 基于runtime
  • 易于集成
  • 扩展属性以 theme_* 开头,便于 IDE 自动补全
  • 支持UIAppearance
  • 自动监听主题切换,更新UI
  • 支持通过字面量设置不同主题,通过索引进行切换
  • 支持使用plist设置主题,可直接通过项目资源加载,或远程下载至沙盒中加载
  • 主题参数配置错误时日志提示
  • 强类型ThemePicker
  • 完整的Demo

安装

CocoaPods

pod 'SwiftTheme'
use_frameworks!

Carthage

github "wxxsw/SwiftTheme"

源文件

拷贝Source文件夹下的所有文件到你的项目中

参考

目前支持的属性


子类会拥有父类的属性,例如UILabel也会拥有UIViewtheme_alpha等属性,这种属性不一一列出

UIView
  • var theme_alpha: ThemeCGFloatPicker?
  • var theme_backgroundColor: ThemeColorPicker?
  • var theme_tintColor: ThemeColorPicker?
UIApplication
  • func theme_setStatusBarStyle(picker: ThemeStatusBarStylePicker, animated: Bool)
UIBarButtonItem
  • var theme_tintColor: ThemeColorPicker?
UILabel
  • var theme_font: ThemeFontPicker?
  • var theme_textColor: ThemeColorPicker?
  • var theme_textAttributes: ThemeStringAttributesPicker?
  • var theme_highlightedTextColor: ThemeColorPicker?
  • var theme_shadowColor: ThemeColorPicker?
UINavigationBar
  • var theme_barStyle: ThemeBarStylePicker?
  • var theme_barTintColor: ThemeColorPicker?
  • var theme_titleTextAttributes: ThemeDictionaryPicker?
UITabBar
  • var theme_barStyle: ThemeBarStylePicker?
  • var theme_barTintColor: ThemeColorPicker?
UITableView
  • var theme_separatorColor: ThemeColorPicker?
UITextField
  • var theme_font: ThemeFontPicker?
  • var theme_keyboardAppearance: ThemeKeyboardAppearancePicker?
  • var theme_textColor: ThemeColorPicker?
  • var theme_placeholderAttributes: ThemeDictionaryPicker?
UITextView
  • var theme_font: ThemeFontPicker?
  • var theme_textColor: ThemeColorPicker?
UIToolbar
  • var theme_barStyle: ThemeBarStylePicker?
  • var theme_barTintColor: ThemeColorPicker?
UISegmentedControl
  • var theme_selectedSegmentTintColor: ThemeColorPicker?
  • func theme_setTitleTextAttributes(_ picker: ThemeStringAttributesPicker?, forState state: UIControl.State)
UISwitch
  • var theme_onTintColor: ThemeColorPicker?
  • var theme_thumbTintColor: ThemeColorPicker?
UISlider
  • var theme_thumbTintColor: ThemeColorPicker?
  • var theme_minimumTrackTintColor: ThemeColorPicker?
  • var theme_maximumTrackTintColor: ThemeColorPicker?
UISearchBar
  • var theme_barStyle: ThemeBarStylePicker?
  • var theme_barTintColor: ThemeColorPicker?
UIProgressView
  • var theme_progressTintColor: ThemeColorPicker?
  • var theme_trackTintColor: ThemeColorPicker?
UIPageControl
  • var theme_pageIndicatorTintColor: ThemeColorPicker?
  • var theme_currentPageIndicatorTintColor: ThemeColorPicker?
UIImageView
  • var theme_image: ThemeImagePicker?
UIActivityIndicatorView
  • var theme_activityIndicatorViewStyle: ThemeActivityIndicatorViewStylePicker?
UIButton
  • 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)
CALayer
  • var theme_backgroundColor: ThemeCGColorPicker?
  • var theme_borderWidth: ThemeCGFloatPicker?
  • var theme_borderColor: ThemeCGColorPicker?
  • var theme_shadowColor: ThemeCGColorPicker?
CAGradientLayer
  • var theme_colors: ThemeAnyPicker?
UIRefreshControl
  • var theme_titleAttributes: ThemeDictionaryPicker?
UIVisualEffectView
  • var theme_effect: ThemeVisualEffectPicker?

Picker


ThemeColorPicker

// 目前支持的颜色格式有:
// "#ffcc00"        RGB十六进制 
// "#ffcc00dd"        +alpha
// "#FFF"            RGB十六进制缩写
// "#013E"            +alphaThemeColorPicker(colors: "#FFFFFF", "#000")
ThemeColorPicker(colors: UIColor.red, UIColor.blue)
ThemeColorPicker(colors: "#FFFFFF", "#000")
ThemeColorPicker.pickerWithColors(["#FFFFFF", "#000"])ThemeColorPicker(keyPath: "someStringKeyPath")
ThemeColorPicker.pickerWithKeyPath("someStringKeyPath")

ThemeImagePicker

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

ThemeCGFloatPicker(floats: 1.0, 0.7)
ThemeCGFloatPicker.pickerWithFloats([1.0, 0.7])ThemeCGFloatPicker(keyPath: "someNumberKeyPath")
ThemeCGFloatPicker.pickerWithKeyPath("someNumberKeyPath")

ThemeCGColorPicker

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

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

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

ThemeStringAttributesPicker(["key": "value"], ["key": "value"])
ThemeStringAttributesPicker.pickerWithAttributes([NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)])ThemeStringAttributesPicker(keyPath: "someStringKeyPath") { (Any?) -> [NSAttributedString.Key: Any]? in ... }

ThemeAttributedStringPicker

ThemeAttributedStringPicker(NSAttributedString(...), NSAttributedString(...))
ThemeAttributedStringPicker.pickerWithAttributedStrings([NSAttributedString(...)])ThemeAttributedStringPicker(keyPath: "someStringKeyPath") { (Any?) -> NSAttributedString? in ... }

ThemeBarStylePicker

ThemeBarStylePicker(styles: .default, .black)
ThemeBarStylePicker.pickerWithStyles([.default, .black])
ThemeBarStylePicker.pickerWithStringStyles(["default", "black"])// 在自定的`Key`中设置指定的`Value`,匹配字符串即可生效
// 可选的值有:"default" 和 "black"
ThemeBarStylePicker(keyPath: "someStringKeyPath")
ThemeBarStylePicker.pickerWithKeyPath("someStringKeyPath")

ThemeStatusBarStylePicker

ThemeStatusBarStylePicker(styles: .default, .lightContent)
ThemeStatusBarStylePicker.pickerWithStyles([.default, .lightContent])
ThemeStatusBarStylePicker.pickerWithStringStyles(["default", "lightContent"])// 在自定的`Key`中设置指定的`Value`,匹配字符串即可生效
// 可选的值有:"default" 和 "lightContent"
ThemeStatusBarStylePicker(keyPath: "someStringKeyPath")
ThemeStatusBarStylePicker.pickerWithKeyPath("someStringKeyPath")

ThemeKeyboardAppearancePicker

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

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

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

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.
  • OCDemoDemo 的 Objective-C 版本.
  • TVOSDemo 用来测试 tvOS 的兼容性.

常见问题

  1. 使用theme_setStatusBarStyle设置状态栏样式时没有任何效果,为什么?

    答:你需要将Info.plist中的View Controller-based status bar appearence设置为NO

  2. 我可以手动取消某个属性的主题吗?

    答:可以,传入nil即可,例如 view.theme_backgroundColor = nil

贡献

Issue

如果你需要帮助或者遇到Bug,请创建一个Issue

Pull Request

期待你的贡献 :D。但是请确保你添加的功能不要偏离主旨,并保证其足够简单易用,如果你不确定可以先通过issue与我们讨论。

Contributors

Gesen, Zhoujun

Lisence

The MIT License (MIT)

上一篇: 我的小家 - 用户协议
下一篇: CTNet
目录