Swift: Night Mode Implementation

There are four key points in the way of realization:

1. Define a protocol that specifies various ways to achieve the theme color.

2. Define a topic class, which contains two key attributes, style and colors. style represents the monitoring of schema changes, while colors can return the corresponding color class according to style.

3. Extend NSObject to define a closure that can be called when the style changes, and notify the global theme of the change.

4. Define the color class: night class and day class.

 

 

Define the protocol ThemeColorProtocol:

protocol ThemeColorProtocol {
    var themeBackgroundColor: UIColor { get }
    var themeNavigationBarColor: UIColor { get }
}

Explanation: Classes that adopt protocols must implement methods of protocols, that is, various thematic colors.

 

Define two subject classes using the protocol ThemeColorProtocol:

DarkColor (Night Mode)

/// Night color
class DarkColor: NSObject, ThemeColorProtocol {
    static let sharedInstance = DarkColor()
    fileprivate override init() {
        super.init()
    }
    
    var themeBackgroundColor: UIColor {
        get {
            return UIColor.black
        }
    }
    
    var themeNavigationBarColor: UIColor {
        return UIColor.red
    }
    
}

 

DefaultColor

/// Default color
class DefaultColor: NSObject, ThemeColorProtocol {
    static let sharedInstance = DefaultColor()
    fileprivate override init() {
        super.init()
    }
    
    var themeBackgroundColor: UIColor {
        get {
            return UIColor.white
        }
    }
    
    var themeNavigationBarColor: UIColor {
        get  {
            return UIColor.white
        }
    }
}

 

Create the theme color class ThemeColor:

class ThemeColor: NSObject {
    static let DarkThemeString = "Dark"
    static let DefaultThemeString = "Default"
    
    static let sharedInstance = ThemeColor()
    fileprivate override init() {
        super.init()
    }
    
    static var colors: ThemeColorProtocol {
        get {
            if ThemeColor().style == ThemeColor.DarkThemeString {
                return DarkColor()
            }
            else {
                return DefaultColor()
            }
        }
    }

    var style: String {
        get {
            //Play back default mode by default
            if let style = UserDefaults.standard.object(forKey: "StyleKey") {
                return style as! String
            }
            else {
                return ThemeColor.DefaultThemeString
            }
        }
        set {
            UserDefaults.standard.set(newValue, forKey: "StyleKey")
            NotificationCenter.default.post(name: NSNotification.Name(rawValue: THEME_CHANGE_NOTIFICAION), object: nil)//Notification is given when the value of style changes. In this way, when we change the theme mode in the settings, we immediately change the APP.
        }
    }
}

Note: This is a singleton class (following the singleton pattern of V2, but it's not really necessary or okay), with two key attributes: colors and style.

  • colors is the ThemeColorProtocol type. Any instance using the ThemeColorProtocol protocol can be assigned to the color attribute, so the corresponding color instance (night class instance or daytime class instance) can be returned here based on the style value. So when we set the background, we can call the corresponding color attributes in the protocol directly through the color attributes, such as:
//Don't judge like that.
if ThemeColor.sharedInstance.style == ThemeColor.DarkThemeString {
self.view.backgroundColor = ThemeColor.colors.themeBackgroundColor
}

//Just write it directly.
self.view.backgroundColor = ThemeColor.colors.themeBackgroundColor

 

  • Style has two values, DarkThemeString and DefaultThemeString, which correspond to night mode and day mode respectively. The get method reads the value of the StyleKey keyword from userDefaults and returns the default value DefaultThemeString if it is nil. The set method saves the new value of the setting to userDefault and notifies the change of the style value (where the same style value is notified before and after)

 

Notify the whole situation:

There are two situations in which we use colors attributes. One is when we actively set colors, such as code.

self.view.backgroundColor = ThemeColor.colors.themeBackgroundColor

Another is to notify global changes when style changes. Generally, we can use kvo to monitor style changes and then execute the above code again in the callback, but for convenience, I extended NSObject to define a closure function.

let THEME_CHANGE_NOTIFICAION = "thmemChangedNotificaion"
//MARK: - Automatically execute when topic changes
extension NSObject {
    fileprivate struct AssociatedKeys {
        static var thmemChanged = "thmemChanged"
    }
    
    
    /// Closure automatically invoked when the current topic changes and the first setup
    public typealias ThemeChangedClosure = @convention(block) (_ style:String) -> Void
    
    /// Closure for automatic invocation
    /// When set up, a notification is registered. When the style keyword of ThemeColor changes, the notification registered here is sent, and the corresponding function of selector is executed.
    var thmemChangedHandler:ThemeChangedClosure? {
        get {
            let closureObject: AnyObject? = objc_getAssociatedObject(self, &AssociatedKeys.thmemChanged) as AnyObject?
            guard closureObject != nil else{
                return nil
            }
            let closure = unsafeBitCast(closureObject, to: ThemeChangedClosure.self)
            return closure
        }
        set{
            guard let value = newValue else{
                return
            }
            let dealObject: AnyObject = unsafeBitCast(value, to: AnyObject.self)
            objc_setAssociatedObject(self, &AssociatedKeys.thmemChanged,dealObject,objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            
            NotificationCenter.default.addObserver(self, selector: #selector(themeChangeNotification), name:NSNotification.Name(rawValue: THEME_CHANGE_NOTIFICAION) , object: nil)
            self.themeChangeNotification()//Execute once by default to set the initial color
        }
    }
     func themeChangeNotification() {
        if let closure = self.thmemChangedHandler {
            closure(ThemeColor().style)
        }
    }
}

Description: Use runtime to register each instance that calls a closure, and execute a closure at the same time as the closure is called to set the initial color.

Keywords: Attribute

Added by ryanwood4 on Sat, 13 Jul 2019 03:59:27 +0300