ThemeManager.swift 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. //
  2. // ThemeManager.swift
  3. // ThemeDemo
  4. //
  5. // Created by 邓永豪 on 2017/8/23.
  6. // Copyright © 2017年 dengyonghao. All rights reserved.
  7. //
  8. import UIKit
  9. let kUpdateTheme = "kUpdateTheme"
  10. let kThemeStyle = "kThemeStyle"
  11. final class ThemeManager: NSObject {
  12. var style: ThemeStyle {
  13. return themeStyle
  14. }
  15. static var instance = ThemeManager()
  16. private var themeBundleName: String {
  17. switch themeStyle {
  18. case .black:
  19. return "blackTheme"
  20. case .o2:
  21. return "o2Theme"
  22. default:
  23. return "defaultTheme"
  24. }
  25. }
  26. // 缓存 image 到内存中,提高重复访问的速度
  27. private let memoryCache = NSCache<NSString, UIImage>()
  28. private var themeStyle: ThemeStyle = .o2
  29. private var themeColors: NSDictionary?
  30. private override init() {
  31. super.init()
  32. if let style = UserDefaults.standard.object(forKey: kThemeStyle) as? Int {
  33. themeStyle = ThemeStyle(rawValue: style)!
  34. } else {
  35. UserDefaults.standard.set(themeStyle.rawValue, forKey: kThemeStyle)
  36. UserDefaults.standard.synchronize()
  37. }
  38. themeColors = getThemeColors()
  39. // 收到内存警告时,移除所有缓存
  40. NotificationCenter.default.addObserver(
  41. self, selector: #selector(clearMemoryCache), name: UIApplication.didReceiveMemoryWarningNotification, object: nil)
  42. }
  43. deinit {
  44. NotificationCenter.default.removeObserver(self)
  45. }
  46. @objc private func clearMemoryCache() {
  47. memoryCache.removeAllObjects()
  48. }
  49. private func getThemeColors() -> NSDictionary? {
  50. let bundleName = themeBundleName
  51. guard let themeBundlePath = Bundle.path(forResource: bundleName, ofType: "bundle", inDirectory: Bundle.main.bundlePath) else {
  52. return nil
  53. }
  54. guard let themeBundle = Bundle(path: themeBundlePath) else {
  55. return nil
  56. }
  57. guard let path = themeBundle.path(forResource: "themeColor", ofType: "txt") else {
  58. return nil
  59. }
  60. let url = URL(fileURLWithPath: path)
  61. let data = try! Data(contentsOf: url)
  62. do {
  63. return try JSONSerialization.jsonObject(with: data, options: [JSONSerialization.ReadingOptions(rawValue: 0)]) as? NSDictionary
  64. } catch {
  65. return nil
  66. }
  67. }
  68. public func updateThemeStyle(_ style: ThemeStyle) {
  69. if themeStyle.rawValue == style.rawValue {
  70. return
  71. }
  72. themeStyle = style
  73. UserDefaults.standard.set(style.rawValue, forKey: kThemeStyle)
  74. UserDefaults.standard.synchronize()
  75. themeColors = getThemeColors()
  76. memoryCache.removeAllObjects()
  77. NotificationCenter.default.post(name: NSNotification.Name(rawValue: kUpdateTheme), object: nil)
  78. }
  79. public func themeColor(_ colorName: String) -> Int {
  80. guard let hexString = themeColors?.value(forKey: colorName) as? String else {
  81. assert(true, "Invalid color key")
  82. return 0
  83. }
  84. let colorValue = Int(strtoul(hexString, nil, 16))
  85. return colorValue
  86. }
  87. // MARK: load image
  88. public func loadImage(_ imageName: String) -> UIImage? {
  89. return loadImage(imageName, themeStyle)
  90. }
  91. public func loadImage(_ imageName: String, _ style: ThemeStyle) -> UIImage? {
  92. if imageName.isEmpty || imageName.count == 0 {
  93. return nil
  94. }
  95. var nameAndType = imageName.components(separatedBy: ".")
  96. var name = nameAndType.first!
  97. let type = nameAndType.count > 1 ? nameAndType[1] : "png"
  98. if let image = memoryCache.object(forKey: name as NSString) {
  99. return image
  100. }
  101. guard let themeBundlePath = Bundle.path(forResource: themeBundleName, ofType: "bundle", inDirectory: Bundle.main.bundlePath) else {
  102. return nil
  103. }
  104. guard let themeBundle = Bundle(path: themeBundlePath) else {
  105. return nil
  106. }
  107. var isImageUnder3x = false
  108. var imagePath = themeBundle.path(forResource: "image/" + name, ofType: type)
  109. let nameLength = name.count
  110. if imagePath == nil && name.hasSuffix("@2x") && nameLength > 3 {
  111. let index = name.index(name.endIndex, offsetBy: -3)
  112. name = name.substring(with: (name.startIndex ..< index))
  113. }
  114. if imagePath == nil && !name.hasSuffix("@2x") {
  115. let name2x = name + "@2x";
  116. imagePath = themeBundle.path(forResource: "image/" + name2x, ofType: type)
  117. if imagePath == nil && !name.hasSuffix("3x") {
  118. let name3x = name + "@3x"
  119. imagePath = themeBundle.path(forResource: "image/" + name3x, ofType: type)
  120. isImageUnder3x = true
  121. }
  122. }
  123. var image: UIImage?
  124. if let imagePath = imagePath {
  125. image = UIImage(contentsOfFile: imagePath)
  126. } else if style != .default {
  127. // 如果当前 bundle 里面不存在这张图片的路径,那就去默认的 bundle 里面找,
  128. // 为什么要这样做呢,因为部分资源在不同 theme 中是一样的,就不需要导入重复的资源,使应用包的大小变大
  129. image = loadImage(imageName, .default)
  130. }
  131. if #available(iOS 8, *){} else {
  132. if isImageUnder3x {
  133. image = image?.scaledImageFrom3x()
  134. }
  135. }
  136. if let image = image {
  137. memoryCache.setObject(image, forKey: name as NSString)
  138. }
  139. return image
  140. }
  141. }
  142. extension UIImage {
  143. func scaledImageFrom3x() -> UIImage {
  144. let theRate: CGFloat = 1.0 / 3.0
  145. let oldSize = self.size
  146. let scaleWidth = CGFloat(oldSize.width) * theRate
  147. let scaleHeight = CGFloat(oldSize.height) * theRate
  148. var scaleRect = CGRect.zero
  149. scaleRect.size = CGSize(width: scaleWidth, height: scaleHeight)
  150. UIGraphicsBeginImageContextWithOptions(scaleRect.size, false, UIScreen.main.scale)
  151. draw(in: scaleRect)
  152. let newImage = UIGraphicsGetImageFromCurrentImageContext()!
  153. UIGraphicsEndImageContext()
  154. return newImage
  155. }
  156. }