Apple dad is always loved and hated, and this year’s Dark mode is sure to give iOS developers a hard time. But it also shows the value of iOS developers again. The unique characteristics of the iOS ecosystem and its constant change and progress, so that iOS developers are always remembered, not completely overwhelmed by the big front-end and multi-terminal unified technology. In that respect, thanks to Apple dad 😘
Anyway, the dark mode API for iOS13 will only be available after iOS13. But most projects stick with the old system in order to get more users. There are a lot of iOS13 dark mode adaptations out there, and the technical points are pretty simple. The main is font color, picture adaptation. After reading, the heart is more sad, iOS13 dark mode adaptation I will, how to do the old system 😂?
You need a lightweight, API-friendly, highly customizable, iOS9+ minimum peels. Don’t worry! My comrade 👬, let me recommend JXTheme for you, it mainly borrowed from iOS13 dark mode adaptation API, using JXTheme you will feel very familiar. And if your app supports at least iOS13, you can easily switch from the JXTheme to the system API.
Making the address
You can go to the Github address first to see the effect. JXTheme making address
Let’s familiarize ourselves with the JXTheme through the entire Dark mode adaptation process:
1. How to elegantly set theme properties
By extending the theme property to the control namespace, similar to SnapKit’s SNP and Kingfisher’s KF, you can concentrate the theme property that supports theme modification. This is more elegant than extending the control’s property theme_backgroundColor directly. The core code is as follows:
view.theme.backgroundColor = ThemeProvider({ (style) in
if style == .dark {
return .white
}else {
return .black
}
})
Copy the code
2. How to configure the corresponding value based on the style passed in
IOS13 APIUIColor(dynamicProvider:
UIColor>). Customize the ThemeProvider structure with the init(_ provider: @escaping ThemePropertyProvider
) initializer. The argument ThemePropertyProvider passed in is a closure defined as typeAlias ThemePropertyProvider
= (ThemeStyle) -> T. This allows for maximum customization for different controls and different property configurations. The core code refers to the sample code in step 1.
3. How do I save the topic property configuration closure
Add the Associated object property providers to the control to store the ThemeProvider. The core code is as follows:
public extension ThemeWrapper where Base: UIView {
var backgroundColor: ThemeProvider<UIColor>? {
set(new) {
ifnew ! =nil {
let baseItem = self.base
let config: ThemeCustomizationClosure = {[weak baseItem] (style) inbaseItem? .backgroundColor = new? .provider(style) }// Stored in the extended property providers
varnewProvider = new newProvider? .config = configself.base.providers["UIView.backgroundColor"] = newProvider
ThemeManager.shared.addTrackedObject(self.base, addedConfig: config)
}else {
self.base.configs.removeValue(forKey: "UIView.backgroundColor")}}get { return self.base.providers["UIView.backgroundColor"] as? ThemeProvider<UIColor>}}}Copy the code
4. How to record controls that support theme properties
To notify controls that support the configuration of theme properties when the theme is switched. By logging the target control when the theme property is set. The core code is this line from step 3:
ThemeManager.shared.addTrackedObject(self.base, addedConfig: config)
Copy the code
It will then be logged to the trackedHashTable property of ThemeManager. Because trackedHashTable is NSHashTable
.init(options:.weakMemory), through weak reference record control, there is no memory problem.
5. How do I switch topics and invoke topic property configuration closures
Thememanager.changetheme (to: style) is used to complete the theme switch, and the method calls the themeProvider.provider theme property configuration closure of the traced control’s providers. The core code is as follows:
public func changeTheme(to style: ThemeStyle) {
currentThemeStyle = style
self.trackedHashTable.allObjects.forEach { (object) in
if let view = object as? UIView {
view.providers.values.forEach { self.resolveProvider($0)}}}}private func resolveProvider(_ object: Any) {
/ / castdown generics
if let provider = object as? ThemeProvider<UIColor> { provider.config? (currentThemeStyle) }else. }Copy the code
preview
features
- Support iOS 9+, let your APP earlier implementation
DarkMode
; - use
theme
Namespace attributes:view.theme.xx = xx
. Say goodbye totheme_xx
Attribute extension usage; - use
ThemeProvider
Passing in the closure configuration. According to differentThemeStyle
Complete the topic attribute configuration to achieve maximum customization; -
ThemeStyle
throughextension
Custom styles are no longer limited tolight
anddark
; - provide
customization
Property, as a callback entry for topic switching, you can flexibly configure any property. No longer limited to what is offeredbackgroundColor
,textColor
And other properties; - Support control Settings
overrideThemeStyle
, which affects its child views; - Provide according to
ThemeStyle
General encapsulation of configuration properties, Plist file static loading, server dynamic loading examples;
Use the sample
extensionThemeStyle
Add custom styles
ThemeStyle provides only a default unspecifiedstyle internally, other business styles need to be added, such as only support light and dark, code as follows:
extension ThemeStyle {
static let light = ThemeStyle(rawValue: "light")
static let dark = ThemeStyle(rawValue: "dark")}Copy the code
Based on using
view.theme.backgroundColor = ThemeProvider({ (style) in
if style == .dark {
return .white
}else {
return .black
}
})
imageView.theme.image = ThemeProvider({ (style) in
if style == .dark {
return UIImage(named: "catWhite")!
}else {
return UIImage(named: "catBlack")! }})Copy the code
Custom property configuration
view.theme.customization = ThemeProvider({[weak self] style in
// You can select any other properties
if style == .dark {
self? .view.bounds =CGRect(x: 0, y: 0, width: 30, height: 30)}else {
self? .view.bounds =CGRect(x: 0, y: 0, width: 80, height: 80)}})Copy the code
Configuration Encapsulation Example
JXTheme is a lightweight base library that provides configuration of theme properties without limiting how resources are loaded. The three examples provided below are for your reference only.
General configuration encapsulation example
In general, there is a UI standard for peels. For example, uilabel.textColor defines three levels as follows:
enum TextColorLevel: String {
case normal
case mainTitle
case subTitle
}
Copy the code
TextColorLevel returns the corresponding configuration closure, which can greatly reduce the amount of configuration code. The global function is as follows:
func dynamicTextColor(_ level: TextColorLevel) -> ThemeProvider<UIColor> {
switch level {
case .normal:
return ThemeProvider({ (style) in
if style == .dark {
return UIColor.white
}else {
return UIColor.gray
}
})
case .mainTitle:
...
case.subTitle: ... }}Copy the code
The following code is used to configure the topic properties:
themeLabel.theme.textColor = dynamicTextColor(.mainTitle)
Copy the code
Example of local Plist file configuration
As with normal configuration encapsulation, except that this method loads the specific value of the configuration from the local Plist file, and the specific code participates in Example’s StaticSourceManager class
Dynamically add themes based on the server
As with normal configuration encapsulation, except that this method loads the configuration concrete value from the server, and the concrete code participates in Example’s DynamicSourceManager class
Stateful controls
Some business requirements have a control with multiple states, such as selected and unchecked. Different states have different configurations for different topics. The configuration code is as follows:
statusLabel.theme.textColor = ThemeProvider({[weak self] (style) in
if self? .statusLabelStatus == .isSelected {// Select a configuration
if style == .dark {
return .red
}else {
return .green
}
}else {
// State another configuration is not selected
if style == .dark {
return .white
}else {
return .black
}
}
})
Copy the code
When the status of the control is updated, the current topic property configuration needs to be refreshed as follows:
func statusDidChange(a){ statusLabel.theme.textColor? .refresh() }Copy the code
If your control supports multiple state properties, such as textColor, backgroundColor, font, etc., you can use the following code to refresh all configured theme properties instead of calling refresh one by one:
func statusDidChange(a) {
statusLabel.theme.refresh()
}
Copy the code
overrideThemeStyle
No matter how theme switch, overrideThemeStyleParentView themeStyle and its child views are dark
overrideThemeStyleParentView.theme.overrideThemeStyle = .dark
Copy the code
Other instructions
Why usetheme
Namespace attributes instead of usingtheme_xx
What about extended properties?
- If you extend a system class by N functions, when you use that class, you will have N extended methods interfering with your choice of function indexing. This is especially true if you are doing other business development than you want to configure topic properties.
- like
Kingfisher
,SnapKit
And other well-known tripartite libraries, all use the namespace attribute implementation of the system class extension, which is a moreSwift
Is worth learning.
Subject Change notification
extension Notification.Name {
public static let JXThemeDidChange = Notification.Name("com.jiaxin.theme.themeDidChangeNotification")}Copy the code
ThemeManager
Store topic configuration by user ID
/// Configure the flag key for the storage. It can be set to the user ID, so that the configuration of different users can be recorded on the same mobile phone. You need to set this property before any other values. public var storeConfigsIdentifierKey: String ="default"
Copy the code
Migrating to the System API guide
If your app supports iOS13 at least, you can follow these guidelines to migrate to the OS solution if necessary. Migrate to system API guide, click to read
Making the address
Finally, to review the Github address, click in for more details. JXTheme making address