Swiftpack.co - perseusrealdeal/PerseusDarkMode as Swift Package

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
See all packages published by perseusrealdeal.
perseusrealdeal/PerseusDarkMode 1.0.4
Library with Dark Mode for iOS 9.3+, macOS 10.9+; Xcode 10.1+.
⭐️ 1
🕓 21 weeks ago
iOS
.package(url: "https://github.com/perseusrealdeal/PerseusDarkMode.git", from: "1.0.4")

Perseus Dark Mode

Actions Status Version Pod Platforms iOS 9 SDK UIKit Swift 5.3 Swift Package Manager compatible CocoaPods manager Carthage manager License

Standalone File Demo App PerseusUISystemKit

Table of contents

Introductory remarks

  1. Build tools & Installation
  2. Solution key statements
  3. Dark Mode table decision
  4. Switching Dark Mode
  5. Catching Dark Mode triggered

Samples:

License


Introductory remarks

Perseus Dark Mode gives a control of Apple's Dark Mode even for early Apple devices starting from iOS 9.0.

1. Build tools & Installation

Tools used for designing the solution:

  • Xcode 13.3.1
  • Device iPod Touch iOS 9.3.5 (5th, 13G36)
  • Device iPad Air iOS 12.5.5 (iPad4,2 model, 16H62)
  • Simulator iPhone 12 mini (iOS 15.4, 19E240)

The solution can be used via swift package manager, CocoaPods dependency manager, Carthage dependency manager and as a standalone single file as well.

File PerseusDarkModeSingle.swift located in the package root and is dedicated for the standalone usage—can be copied and pasted into a host project tree under the same license.

2. Solution key statements

Dark Mode is a Singleton object

public class AppearanceService {

    public static var shared: DarkMode = { DarkMode() }()
    private init() { }

}

Dark Mode is a complex object

public protocol DarkModeProtocol {

    var Style: AppearanceStyle { get }
    var SystemStyle: SystemStyle { get }

    dynamic var StyleObservable: Int { get }

}

extension DarkMode: DarkModeProtocol { }

Dark Mode is hosted as a property in a screen object

public extension UIResponder { var DarkMode: DarkModeProtocol { AppearanceService.shared }}

3. Dark Mode table decision

Dark Mode option values

public enum DarkModeOption: Int {

    case auto = 0
    case on   = 1
    case off  = 2

}

Dark Mode System values

public enum SystemStyle: Int {

    case unspecified = 0
    case light       = 1
    case dark        = 2

}

Dark Mode decision table

Dark Mode decision table makes decision on Appearance style that can be either light or dark:

public enum AppearanceStyle: Int {

    case light = 0
    case dark  = 1

}

Dark Mode default value is light:

public let DARK_MODE_STYLE_DEFAULT = AppearanceStyle.light
auto on off
.unspecified default dark light
.light light dark light
.dark dark dark light

In case if dark mode option is in auto and system style is .unspecified, default value is applied for iOS 12 and eariler, but, for iOS 13 and higher, device system appearance mode is.

Apps that are based on Perseus Dark Mode rely on AppearanceStyle as a business matter value available for accessing via UIResponder extension:

import UIKit

import PerseusDarkMode

class MyView: UIView  { 
    func functionName() { 
        print("\(self.DarkMode.Style)")
    } 
}

4. Switching Dark Mode

Case: Manually

Inside your app there is only one way to let it know that you'd like to change Dark Mode. Use DarkModeUserChoice hosted as a property in AppearanceService to assign the value of Dark Mode you want.

So, in your code it should look like this:

let choice = DarkModeOption.auto

AppearanceService.DarkModeUserChoice = choice

AppearanceService.makeUp()

And do not forget call makeUp() if you want to be notified via NotificationCenter.

Case: Via System

To match the system appearance mode call AppearanceService.processTraitCollectionDidChange(_:) method once in traitCollectionDidChange(_:) of the main screen (UIViewController or UIWindow). The sample is below:

import UIKit
import PerseusDarkMode

class MainViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        configure()

        AppearanceService.register(stakeholder: self, selector: #selector(makeUp))
    }

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection? {
        super.traitCollectionDidChange(previousTraitCollection)

        if #available(iOS 13.0, *) {
            AppearanceService.processTraitCollectionDidChange(previousTraitCollection)
        }
    }

    @objc private func makeUp() {
        // Point to define a reaction to Dark Mode event is here
    }

    private func configure() { }
}

5. Catching Dark Mode triggered

Case: Using KVO

Create an observer somewhere in your code like this:

 var observer: DarkModeObserver?
 
 observer = DarkModeObserver()

Then, give it a closure to run your code each time when Dark Mode is changing:


observer?.action = { newStyle in 

        // Point to define a reaction to Dark Mode event is here

    }

or like this:

var observer = DarkModeObserver { newStyle in

        // Point to define a reaction to Dark Mode event is here

    }

Case: Getting informed by NotificationCenter

To get notified by NotificationCenter your object should be registered with AppearanceService:

import UIKit

class MyView: UIView { 
    @objc func makeUp() { 
        // Point to define a reaction to Dark Mode event is here
    } 
}

let view = MyView()

AppearanceService.register(stakeholder: view, selector: #selector(view.makeUp))

Call AppearanceService.makeUp()

Use AppearanceService.makeUp() to call all selected makeUp methods:

AppearanceService.makeUp()

It should be called first time in didFinishLaunchingWithOptions:

import UIKit

import PerseusDarkMode

class AppDelegate: UIResponder { var window: UIWindow? }

extension AppDelegate: UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions
                     launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        // Init the app's window
        window = UIWindow(frame: UIScreen.main.bounds)

        // Give it a root view for the first screen
        window!.rootViewController = MainViewController.storyboardInstance()
        window!.makeKeyAndVisible()

        // And, finally, apply a new style for all screens
        AppearanceService.makeUp()

        return true
    }
}

6. Sample Use Case of Dark Mode

import UIKit
import PerseusDarkMode

class MainViewController: UIViewController {

    let darkModeObserver = DarkModeObserver()

    override func viewDidLoad() {
        super.viewDidLoad()
        configure()

        AppearanceService.register(stakeholder: self, selector: #selector(makeUp))

        darkModeObserver.action = { newStyle in

                // Point to define a reaction to Dark Mode event is here
                print("\(newStyle), \(self.DarkMode.Style)")
            }
    }

    override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)
        
        if #available(iOS 13.0, *) {
            AppearanceService.processTraitCollectionDidChange(previousTraitCollection)
        }
    }

    @objc private func makeUp() {
        // Point to define a reaction to Dark Mode event is here
        view.backgroundColor = .systemRed
    }

    private func configure() { }
}

In addition to sample use case

If your view or view controller is declared as a lazy one or a sub view like UITableViewCell it's not bad to add the following condition after registering:

if AppearanceService.isEnabled { makeUp() }

For instance, here is a definition of some exemplar of UITableViewCell:

import UIKit
import PerseusDarkMode

class MemberTableViewCell: UITableViewCell {
    override func awakeFromNib() {
        super.awakeFromNib()
        configure()

        AppearanceService.register(stakeholder: self, selector: #selector(makeUp))
        if AppearanceService.isEnabled { makeUp() }
    }

    private func configure() { }

    @objc private func makeUp() {
        // Point to define a reaction to Dark Mode event is here
        backgroundColor = .systemGray
    }
}

License

MIT License

Copyright (c) 2022 Mikhail Zhigulin of Novosibirsk

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.

GitHub

link
Stars: 1
Last commit: 3 hours ago
jonrohan Something's broken? Yell at me @ptrpavlik. Praise and feedback (and money) is also welcome.

Release Notes

v1.0.4
21 weeks ago
  • Missing helper included to the single

Happy coding ^_^

P.S. Using exact version is recommended.

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics