Skip to content

PerseusRealDeal/PerseusDarkMode

Repository files navigation

Perseus Dark Mode

THE DARKNESS YOU CAN FORCE

Actions Status Style Version Platforms Xcode 14.2 Swift 5.7

A3 EnvironmentCHANGELOGMIT License


Usages

1: Build Light/Dark/Auto option.
2: Be awared of Dark Mode changes.

Integration

Standalone Swift Package Manager compatible

Dependencies

ConsolePerseusLogger

Support Code

Standalone License

TheDarknessSupport.swift is a peace of code a widly helpful in accord with PDM.

PDM in Use

Approbation:

Business:

Contents

Our Terms

Acronym Stands for
CPL Console_Perseus_Logger
PDM Perseus_Dark_Mode
PGK Perseus_Geo_Kit
A3 Apple_Apps_Approbation
T3 The_Technological_Tree
P2P Person_to_Person

In brief

The great home-made product to accomplish Dark Mode switching task. PDM is a single author and personale solution developed in P2P relationship paradigm.

macOS app iOS app iOS Settings bundle

Important

The macOS App scenes taken from the motion picture The Hobbit based on the novel by J.R.R. Tolkien.
The iOS App scenes taken from the motion picture The Lord of The Rings based on the novel by J.R.R. Tolkien.

Requirements

To build:

But as the single source code file TheDarknessStar.swift PDM can be used even in Xcode 10.1.

First-party software

MIT

Type Name
Star ConsolePerseusLogger / 1.7.1

Third-party software

MIT

Type Name
Style SwiftLint / v0.57.0 for Monterey+
Script SwiftLint Shell Script to run SwiftLint
Action cirruslabs/swiftlint-action@v1
Type Name
Action mxcl/xcodebuild@v3

Installation

Step 1: Import the Darkness either with SPM or standalone

Standalone: the single source code file TheDarknessStar.swift

Swift Package Manager: https://github.com/perseusrealdeal/PerseusDarkMode

Cocoa macOS project

Step 2: In the AppDelegate when applicationDidFinishLaunching call force

import Cocoa
import PerseusDarkMode

class AppDelegate: NSObject, NSApplicationDelegate {

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        DarkModeAgent.force(DarkModeUserChoice)
    }
}

Step 3: Register the MainWindowController for Dark Mode changes

import Cocoa
import PerseusDarkMode

class MainWindowController: NSWindowController, NSWindowDelegate {

    override func windowDidLoad() {
        super.windowDidLoad()

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

    @objc private func makeUp() {
    
    // Runs every time if Dark Mode changes.
    // The current DarkMode value is reliable here.
    
    let isDark = DarkMode.style == .dark
    let _ = isDark ? "It's dark" : "No dark"
    
    }
}

UIKit iOS project

Step 2: In the AppDelegate when didFinishLaunchingWithOptions call force

import UIKit
import PerseusDarkMode

class AppDelegate: UIResponder { var window: UIWindow? }

extension AppDelegate: UIApplicationDelegate {

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

        // Register Settings Bundle
        registerSettingsBundle()

        // 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()
        
        DarkModeAgent.force(DarkModeUserChoice)
        
        return true
    }
    
    func applicationDidBecomeActive(_ application: UIApplication) {

        // Actualize Dark Mode style to Settings Bundle
        if let choice = DarkModeAgent.isDarkModeSettingsKeyChanged() {
            DarkModeAgent.force(choice)
        }
    }
}

Step 3: Register the MainViewController and process traitCollectionDidChange for DarkMode changes

import UIKit
import PerseusDarkMode

class MainViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

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

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

        if #available(iOS 13.0, *) {
            DarkModeAgent.processTraitCollectionDidChange(previousTraitCollection)
        }
    }
    
    @objc private func makeUp() {
    
    // Runs every time if Dark Mode changes.
    // The current DarkMode value is reliable here, DarkModeAgent selector registered.
    
    let isDark = DarkMode.style == .dark
    let _ = isDark ? "It's dark" : "No dark"
    
    }
}

Usage

Force Dark Mode

The Dark Mode of your app can be easely forced in .on, .off or .auto just call method force of DarkModeAgent like this.

DarkModeAgent.force(.off) // It's a sunny day for the app.

The force will change the appearance of your app immediately including system components and will make run all custom DarkMode code makeUp().

Get awared of DarkMode changes

To declare custom DarkMode sensitive code that runs every time if DarkMode Changes register the object or create a DarkMode trigger:

Use Case 1: Register an object to be notified on changes

class DarkModeSensitiveObject {

    init() {
        DarkModeAgent.register(stakeholder: self, selector: #selector(makeUp))
    }

    @objc private func makeUp() {
        // Runs evary time if Dark Mode changes.
    }
}

Use Case 2: Create a DarkMode trigger and give it an action

class DarkModeSensitiveObject {

    private var theDarknessTrigger = DarkModeObserver()

    init() {
        theDarnessTrigger.action = { _ in
            self.makeUp()
        }
    }

    private func makeUp() {
        // Runs evary time if Dark Mode changes.
    }
}

DarkMode change sample

Use Case: Custom DarkMode sensitive color

import PerseusDarkMode

#if canImport(UIKit)
import UIKit
#elseif canImport(Cocoa)
import Cocoa
#endif

#if os(iOS)
public typealias Color = UIColor
#elseif os(macOS)
public typealias Color = NSColor
#endif

public func rgba255(_ red: CGFloat,
                    _ green: CGFloat,
                    _ blue: CGFloat,
                    _ alpha: CGFloat = 1.0) -> Color {
    return Color(red: red/255, green: green/255, blue: blue/255, alpha: alpha)
}

extension Color {
    public static var customRed: Color {
        return DarkModeAgent.shared.style == .light ?
            rgba255(255, 59, 48) : rgba255(255, 69, 58)
    }
}

And use custom DarkMode sensitive color:

// Runs every time if the DarkMode changes. 
// Use KVO (DarkModeObserver) or be registered with DarkModeAgent. 
@objc private func makeUp() {
    self.backgroundColor = .customRed
}

`Account points

License

MIT License, see LICENSE for details.

Copyright © 7530 - 7534 Mikhail A. Zhigulin of Novosibirsk
Copyright © 7533 - 7534 PerseusRealDeal

  • The year starts from the creation of the world according to a Slavic calendar.
  • September, the 1st of Slavic year. For instance, "Sep 01, 2025" is the beginning of 7534.

Other required licenses details

© 2025 The SwiftLint Contributors for SwiftLint
© GitHub for GitHub Action cirruslabs/swiftlint-action@v1
© 2021 Alexandre Colucci, geteimy.com for Shell Script SucceedsPostAction.sh

Credits

Balance and Control Mikhail Zhigulin
Source Code Mikhail Zhigulin
Documentation Mikhail Zhigulin
Approbation Mikhail Zhigulin
English Mikhail Zhigulin

Contributing

Note

The product is constructed in P2P relationship paradigm that means the only one single and the same face in the product team during all development process stages.

Bug reports are welcome, create an issue and give details.

Author

© Mikhail A. Zhigulin of Novosibirsk

Contact

`