Introduction - Demos - Installation - Documents - FAQ - Contribution - 中文文档
Running:open
SwiftTheme.xcworkspace
, run targetPlistDemo
As the project requirement, we need to add night mode to our app 节操精选. It's not as simple as changing brightness or alpha only on the top-level view. In fact, it need a entirely new interface: different colors, different alpha, different image cuts. More accurately, so called "night mode" is themes/skinning feature that can switch between a bright theme and a dark theme.
How to achieve this? Maybe we can set a global variable represents current selected theme, use different background colors or different image cuts based on the variable during the controllers initialization. But how to deal with the views that have been initialized? Yes, maybe you think we can use notification to change their colors or image cuts. Thought here, you should premonition that your controllers will be filled up with notification register/unregister, if...else and UI updating codes. Worse, if you forget to unregister the notifications app may crash.
After some consideration, we put forward higher requirements on the task: create a simple and reusable themes/skinning framework, here as you see.
Make SwiftTheme a simple, powerful, high-performance, extensible themes/skinning framework. Provide a unified solution for iOS.
Vary background color of UIView according to the theme setting:
view.theme_backgroundColor = ["#FFF", "#000"]
Vary text color of UILable and UIButton:
label.theme_textColor = ["#000", "#FFF"]
button.theme_setTitleColor(["#000", "#FFF"], forState: .Normal)
Vary image of UIImageView:
imageView.theme_image = ["day", "night"]
// It's ok by using UIImage instances if you don't want to use image names.
imageView.theme_image = ThemeImagePicker(images: image1, image2)
A miracle happens after you executing the one line of code below!
// these numbers represent the parameters' index.
// eg. "view.theme_backgroundColor = ["#FFF", "#000"]", index 0 represents "#FFF", index 1 represents "#000"
ThemeManager.setTheme(index: isNight ? 1 : 0)
Get current theme index.
ThemeManager.currentThemeIndex // Readonly
Index mode is a fast way for the situation: a few themes, but not many, no need to download more new themes.
Notice About Literal:
// Wrong example:
let colors = ["#FFF", "#000"]
view.theme_backgroundColor = colors
// You should write like this:
view.theme_backgroundColor = ["#FFF", "#000"]
// or this:
let colorPickers: ThemeColorPicker = ["#FFF", "#000"]
view.theme_backgroundColor = colorPickers
Because theme_backgroundColor accepts an argument of type ThemeColorPicker,not Array. Nevertheless, "view.theme_backgroundColor = ["#FFF", "#000"]" does the same as initializing an instance of ThemeColorPicker by "Literal" and passing it to the theme_backgroundColor.
You may want to make you app download and install an indefinite number of themes. To fulfill this requirement, we provide the plist mode. Simply put, you write the configurations in a plist file, such as colors, image cuts and so on. Then, you can use their keys in the logic code. So, the plist file and the resource files it used constitute a theme package.
Usage demo of plist mode.
view.theme_backgroundColor = ThemeColorPicker(keyPath: "Global.backgroundColor")
imageView.theme_image = ThemeImagePicker(keyPath: "SelectedThemeCell.iconImage")
Similar with the index mode. Only the specific parameters become keys. And as such, we give it the extension ability.
The plist file name is the first paramter of the switching method. In this example, the plist file and other resource files are in the application bundle. It's also ok if they are in sandbox.
ThemeManager.setTheme(plistName: "Red", path: .MainBundle)
plist mode allow you install more themes without modifying logic code. So, you can add the feature that, downloading and installing themes for your app.
the screenshots of the plist and image files we used above:
Fully compatible with Objective-C, usage demo:
lbl.theme_backgroundColor = [ThemeColorPicker pickerWithColors:@[@"#FAF9F9", @"#E2E2E2"]];
- Written in Swift
- Fully compatible with Objective-C
- Based on runtime
- Simple integration
- Extension property prefix with "theme_*", friendly with IDE auto-completion
- Support UIAppearance
- Index mode, fast integration
- Plist mode, extend infinite themes
- Friendly error logs
- Strongly typed ThemePicker, detect errors during compilling
- Complete demos
Swift code is built as dynamic framework by CocoaPods, Carthage and the project itself, while dynamic frameworks works on iOS8+
If you want it work on iOS7, you should copy the source files into you project
pod 'SwiftTheme'
use_frameworks!
github "jiecao-fm/SwiftTheme"
Copy all the files in "Source" folder into your project
Note:①
usage of index mode ②
usage of plist mode
SwiftTheme provide new properties for views, they all beigin with theme_
. Such as theme_backgroundColor
corresponds backgroundColor
.
①
view.theme_backgroundColor = ThemeColorPicker(colors: "#FFF", "#000")
view.theme_image = ThemeImagePicker(names: "day", "night")
②
view.theme_backgroundColor = ThemeColorPicker(keyPath: "SomeColorKeyPath")
view.theme_image = ThemeImagePicker(keyPath: "SomeImageKeyPath")
Different type of properties receive different type of Pickers. Thus, IDE will warn you if you pass a wrong parameter.
When you switch themes, all the theme_
properties you set will update with animation. Usage:
①
ThemeManager.setTheme(index: 0) // ThemePickers will use the first parameter, eg. "#FFF" "day"
ThemeManager.setTheme(index: 1) // ThemePickers will use the second parameter, eg. "#000" "night"
②
// use "day.plist" in the appllication bundle as the theme configuration file.
// In this mode, SwiftTheme will find the resource files in the appllication bundle.
ThemeManager.setTheme(plistName: "day", path: .MainBundle)
// use "night.plist" in the sandbox as the theme configuration file, "someURL" is its file path.
// In this mode, SwiftTheme will find the resource files in the same path.
ThemeManager.setTheme(plistName: "night", path: .Sandbox(someURL))
// use a dictionary as the theme configuration, but find resource files in the sandbox.(Not recommend)
ThemeManager.setTheme(dict: dict, path: .Sandbox(someURL))
SwiftTheme posts a notification named ThemeUpdateNotification
when theme changes, you can observe this notification anywhere and do whatever you want:
NotificationCenter.default.addObserver(
self,
selector: #selector(doSomethingMethod),
name: NSNotification.Name(rawValue: ThemeUpdateNotification),
object: nil
)
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomethingMethod) name:@"ThemeUpdateNotification" object:nil];
Child classes inherit the properties from their super class, such as UILabel have theme_alpha inherited from UIView. These properties will not be list in child classes below.
- 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_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_textColor: ThemeColorPicker?
- var theme_font: ThemeFontPicker?
- var theme_textColor: ThemeColorPicker?
- var theme_barStyle: ThemeBarStylePicker?
- var theme_barTintColor: ThemeColorPicker?
- 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)
- var theme_backgroundColor: ThemeCGColorPicker?
- var theme_borderWidth: ThemeCGFloatPicker?
- var theme_borderColor: ThemeCGColorPicker?
- var theme_shadowColor: ThemeCGColorPicker?
// supported formats:
// "#ffcc00" RGB
// "#ffcc00dd" RGBA
// "#FFF" RGB in short
// "#013E" RGBA in short
①
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.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)])
②
// Reading font from plist is not supported now
①
ThemeDictionaryPicker(dicts: ["key": "value"], ["key": "value"])
ThemeDictionaryPicker.pickerWithDicts([["key": "value"], ["key": "value"]])
②
// Reading dictionary from plist is not supported now
①
ThemeBarStylePicker(styles: .default, .black)
ThemeBarStylePicker.pickerWithStyles([.default, .black])
ThemeBarStylePicker.pickerWithStringStyles(["default", "black"])
②
// name the key you like, but the available values are "default" and "black"
ThemeBarStylePicker(keyPath: "someStringKeyPath")
ThemeBarStylePicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeStatusBarStylePicker(styles: .default, .lightContent)
ThemeStatusBarStylePicker.pickerWithStyles([.default, .lightContent])
ThemeStatusBarStylePicker.pickerWithStringStyles(["default", "lightContent"])
②
// name the key you like, but the available values are "default" and "lightContent"
ThemeStatusBarStylePicker(keyPath: "someStringKeyPath")
ThemeStatusBarStylePicker.pickerWithKeyPath("someStringKeyPath")
①
ThemeActivityIndicatorViewStylePicker(styles: .whiteLarge, .white, .gray)
ThemeActivityIndicatorViewStylePicker.pickerWithStyles([.whiteLarge, .white, .gray])
ThemeActivityIndicatorViewStylePicker.pickerWithStringStyles(["whiteLarge", "white", "gray"])
②
// name the key you like, but the available values are "whiteLarge", "white" and "gray"
ThemeActivityIndicatorViewStylePicker(keyPath: "someStringKeyPath")
ThemeActivityIndicatorViewStylePicker.pickerWithKeyPath("someStringKeyPath")
Download this project and find more. There are two demo targets:
Demo
shows how to use index mode and how to save the last selection of themes and other general usages.PlistDemo
shows how to use plist mode and how to download themes that packaged in zip files.
-
Why theme_setStatusBarStyle doesn't work as expected?
You will need to
Info.plist
inView Controller-based status bar appearence
set toNO
. -
Can I manually cancel the theme of a property?
Sure, make
nil
. example:view.theme_backgroundColor = nil
.
If you find a bug or need a help, you can create a issue
Expect your pull request :D. But please make sure it's needed by most developers and make it simple to use. If you are not sure you can create a issue and let's discuss before coding.
The MIT License (MIT)
Copyright (c) 2016 节操精选 http://jiecao.fm
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.