换肤
换肤的应用场景?
一般应用在某些APP , 在节假日更换主题, 或者切换白天或者夜间模式时使用.
一般实现方式
- 实现基本的换肤功能, 直接替换图片,无缓存主题
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var backImageView: UIImageView!
// 当前代码, 非常垃圾
// 代码冗余度高
override func viewDidLoad() {
super.viewDidLoad()
guard let theme = UserDefaults.standard.value(forKey: "theme") else {return}
let themeStr = theme as! String
backImageView.image = UIImage(named: themeStr + "_back.png")
button.setImage(UIImage(named: themeStr + "_icon.png"), for: UIControlState())
}
@IBAction func guoqing() {
backImageView.image = UIImage(named: "guoqing_back.png")
button.setImage(UIImage(named: "guoqing_icon.png"), for: UIControlState())
// button.setBackgroundImage(UIImage(named: "guoqing_icon.png"), forState: UIControlState.Normal)
UserDefaults.standard.setValue("guoqing", forKey: "theme")
UserDefaults.standard.synchronize()
}
@IBAction func chunjie() {
backImageView.image = UIImage(named: "chunjie_back.png")
button.setImage(UIImage(named: "chunjie_icon.png"), for: UIControlState())
UserDefaults.standard.setValue("chunjie", forKey: "theme")
UserDefaults.standard.synchronize()
}
@IBAction func zhongqiu() {
backImageView.image = UIImage(named: "zhongqiu_back.png")
button.setImage(UIImage(named: "zhongqiu_icon.png"), for: UIControlState())
UserDefaults.standard.setValue("zhongqiu", forKey: "theme")
UserDefaults.standard.synchronize()
}
}
- 使用用户偏好缓存当前皮肤主题, 方便下次进来后依然是上次所选主题。但代码冗余, 重用性差
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var backImageView: UIImageView!
// 当前代码, 非常垃圾
// 重用性比较低
override func viewDidLoad() {
super.viewDidLoad()
guard let theme = getTheme() else {return}
setImage(theme)
}
@IBAction func guoqing() {
setImage("guoqing")
save("guoqing")
}
@IBAction func chunjie() {
setImage("chunjie")
save("chunjie")
}
@IBAction func zhongqiu() {
setImage("zhongqiu")
save("zhongqiu")
}
func getTheme() -> String? {
guard let theme = UserDefaults.standard.value(forKey: "theme") else {return nil}
let themeStr = theme as! String
return themeStr
}
func save(_ theme: String) -> () {
UserDefaults.standard.setValue(theme, forKey: "theme")
UserDefaults.standard.synchronize()
}
func setImage(_ theme: String) -> () {
backImageView.image = UIImage(named: theme + "_back.png")
button.setImage(UIImage(named: theme + "_icon.png"), for: UIControlState())
}
}
在控制器中直接抽取对应方法, 简化代码。缺点:代码重用性差, 如果在别的控制器需要获取或者设置当前主题, 有需要将代码拷贝一份。逻辑分明不明确, 控制器不应当关心 具体的如何存储主题, 获取主题
抽取公共的皮肤管理类, 简化控制器逻辑。
- 存在问题:需要写主题名称, 一个字符串容易写错。
- 解决方案:由工具类提供一个枚举类型, 供外界直接选择
ThemeTool.swift
import Foundation
class ThemeTool: NSObject {
// 工具类内部存在的问题
// "theme" : 是一个字符串, 既然是字符串, 就有可能写错
// 主题提供, 没有明确指示, 外界直接传递一个字符串过来
class func save(_ theme: String) -> () {
UserDefaults.standard.setValue(theme, forKey: "theme")
UserDefaults.standard.synchronize()
}
class func getTheme() -> String? {
guard let theme = UserDefaults.standard.value(forKey: "theme") else {return nil}
let themeStr = theme as! String
return themeStr
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var backImageView: UIImageView!
// 当前代码, 非常垃圾
// 工具类内部存在的问题
//
override func viewDidLoad() {
super.viewDidLoad()
guard let theme = ThemeTool.getTheme() else {return}
setImage(theme)
}
@IBAction func guoqing() {
setImage("guoqing")
ThemeTool.save("guoqing")
}
@IBAction func chunjie() {
setImage("chunjie")
ThemeTool.save("chunjie")
}
@IBAction func zhongqiu() {
setImage("zhongqiu")
ThemeTool.save("zhongqiu")
}
func setImage(_ theme: String) -> () {
backImageView.image = UIImage(named: theme + "_back.png")
button.setImage(UIImage(named: theme + "_icon.png"), for: UIControlState())
}
}
- 优化工具类, 使用全局常亮, 和枚举
- 存在问题:外界获取到主题名称主要的目的, 就是要拼接该主题下的图片(背景图片, 按钮图片), 这个不是控制器的事情
- 解决方案:由工具类提供当前主题下的背景图片, 和 按钮图片等等
ThemeTool.swift
import Foundation
let kTheme = "theme"
// OC 枚举, 整型
// swift ,枚举, 是任意类型
enum ThemeType: String {
case Zhongqiu = "zhongqiu_"
case Guoqing = "guoqing_"
case Chunjie = "chunjie_"
}
class ThemeTool: NSObject {
// 工具类内部存在的问题
// "theme" : 是一个字符串, 既然是字符串, 就有可能写错
// 主题提供, 没有明确指示, 外界直接传递一个字符串过来
class func save(_ theme: ThemeType) -> () {
UserDefaults.standard.setValue(theme.rawValue, forKey: kTheme)
UserDefaults.standard.synchronize()
}
class func getTheme() -> ThemeType? {
guard let theme = UserDefaults.standard.value(forKey: kTheme) else {return .none}
let themeStr = theme as! String
return ThemeType(rawValue: themeStr)
}
}
ViewController.swift
...
@IBAction func guoqing() {
setImage(.Guoqing)
ThemeTool.save(ThemeType.Guoqing)
}
@IBAction func chunjie() {
setImage(.Chunjie)
ThemeTool.save(ThemeType.Chunjie)
}
@IBAction func zhongqiu() {
setImage(.Zhongqiu)
ThemeTool.save(ThemeType.Zhongqiu)
}
...
- 再次优化工具类, 增加提供当前主题下对应图片的方法
- 存在问题:图片素材名称跟主题名称相关, 美工作图命名比较复杂(对我们开发人员没有多大影响)
- 解决方案:改成靠文件夹进行区分不同主题
- 使用文件夹, 对主题图片进行分类。虚拟文件夹, 无法在APP mainBundle中创建物理路径
- 直接使用bundle文件夹
ThemeTool.swift
import Foundation
import UIKit
let kTheme = "theme"
// OC 枚举, 整型
// swift ,枚举, 是任意类型
enum ThemeType: String {
case Zhongqiu = "zhongqiu/"
case Guoqing = "guoqing/"
case Chunjie = "chunjie/"
}
enum ImageType: String {
case Back = "back.png"
case Icon = "icon.png"
}
class ThemeTool: NSObject {
class func getImage(_ type: ImageType) -> UIImage? {
// 1. 获取当前主题
guard let theme = getTheme() else {return nil}
let themeStr = theme.rawValue
// 2. 拼接图片名称
// back, icon
let imageTypeStr = type.rawValue
let imageName = "images.bundle/" + themeStr + imageTypeStr
return UIImage(named: imageName)
}
// 工具类内部存在的问题
// "theme" : 是一个字符串, 既然是字符串, 就有可能写错
// 主题提供, 没有明确指示, 外界直接传递一个字符串过来
class func save(_ theme: ThemeType) -> () {
UserDefaults.standard.setValue(theme.rawValue, forKey: kTheme)
UserDefaults.standard.synchronize()
}
class func getTheme() -> ThemeType? {
guard let theme = UserDefaults.standard.value(forKey: kTheme) else {return .none}
let themeStr = theme as! String
return ThemeType(rawValue: themeStr)
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var button: UIButton!
@IBOutlet weak var backImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// 获取主题的目的, 就是为了获取当前主题下的背景图片, 或者前景图片
setImage()
}
@IBAction func guoqing() {
ThemeTool.save(ThemeType.Guoqing)
setImage()
}
@IBAction func chunjie() {
ThemeTool.save(ThemeType.Chunjie)
setImage()
}
@IBAction func zhongqiu() {
ThemeTool.save(ThemeType.Zhongqiu)
setImage()
}
func setImage() -> () {
backImageView.image = ThemeTool.getImage(ImageType.Back)
button.setImage(ThemeTool.getImage(ImageType.Icon), for: UIControlState())
}
}
- 实现主题颜色
ThemeTool.swift
import Foundation
import UIKit
let kTheme = "theme"
// OC 枚举, 整型
// swift ,枚举, 是任意类型
enum ThemeType: String {
case Zhongqiu = "zhongqiu/"
case Guoqing = "guoqing/"
case Chunjie = "chunjie/"
}
enum ImageType: String {
case Back = "back.png"
case Icon = "icon.png"
}
enum ColorType: String {
case LableForeColor = "labelFC"
case ButtonColor = "buttonColor"
}
class ThemeTool: NSObject {
class func getColor(_ type: ColorType) -> UIColor? {
// 1. 获取当前主题
guard let theme = getTheme() else {return nil}
let themeStr = theme.rawValue
// 颜色的存储方案
// r255 g 222,b 123
// 2. 取出不同主题对应路径下面的颜色配置文件
let fullPath = Bundle.main.path(forResource: "ColorConfig.plist", ofType: nil, inDirectory: "images.bundle/" + themeStr)
// 3. 加载里面所有的颜色(字典 key, value)
let dic = NSDictionary(contentsOfFile: fullPath!) ?? NSDictionary()
// 4. 根据颜色类型, 取出字典里面对应的值
// 21,123,233
guard let colorStr = dic[type.rawValue] else {return nil}
let colorStr2 = colorStr as! NSString
// 5. 分解字符串, 创建颜色对象,
let colorArray = colorStr2.components(separatedBy: ",")
if colorArray.count < 3 {
return nil
}
let red = CGFloat(Int(colorArray[0])!)
let green = CGFloat(Int(colorArray[1])!)
let blue = CGFloat(Int(colorArray[2])!)
let color = UIColor(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: 1)
return color
}
class func getImage(_ type: ImageType) -> UIImage? {
// 1. 获取当前主题
guard let theme = getTheme() else {return nil}
let themeStr = theme.rawValue
// 2. 拼接图片名称
// back, icon
let imageTypeStr = type.rawValue
let imageName = "images.bundle/" + themeStr + imageTypeStr
return UIImage(named: imageName)
}
// 工具类内部存在的问题
// "theme" : 是一个字符串, 既然是字符串, 就有可能写错
// 主题提供, 没有明确指示, 外界直接传递一个字符串过来
class func save(_ theme: ThemeType) -> () {
UserDefaults.standard.setValue(theme.rawValue, forKey: kTheme)
UserDefaults.standard.synchronize()
}
class fileprivate func getTheme() -> ThemeType? {
guard let theme = UserDefaults.standard.value(forKey: kTheme) else {return .none}
let themeStr = theme as! String
return ThemeType(rawValue: themeStr)
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController {
@IBOutlet weak var label: UILabel!
@IBOutlet weak var button: UIButton!
@IBOutlet weak var backImageView: UIImageView!
// 当前代码, 非常垃圾
// 工具类内部存在的问题
//
override func viewDidLoad() {
super.viewDidLoad()
// 获取主题的目的, 就是为了获取当前主题下的背景图片, 或者前景图片
setImage()
setLabelColor()
}
@IBAction func guoqing() {
ThemeTool.save(ThemeType.Guoqing)
setImage()
setLabelColor()
}
@IBAction func chunjie() {
ThemeTool.save(ThemeType.Chunjie)
setImage()
setLabelColor()
}
@IBAction func zhongqiu() {
ThemeTool.save(ThemeType.Zhongqiu)
setImage()
setLabelColor()
}
func setImage() -> () {
// 获取当前主题下, 不同类型的图片(背景, 或者前景)
backImageView.image = ThemeTool.getImage(ImageType.Back)
button.setImage(ThemeTool.getImage(ImageType.Icon), for: UIControlState())
}
func setLabelColor() -> () {
// 获取当前主题下的, 不同类型颜色
label.textColor = ThemeTool.getColor(ColorType.LableForeColor)
button.backgroundColor = ThemeTool.getColor(ColorType.ButtonColor)
}
}