Skip to content

PerseusRealDeal/PerseusDarkMode

Repository files navigation

PerseusDarkMode — Xcode 14.2+

Actions Status Style Version Platforms Xcode 14.2 Swift 5.7 License

Home-made product. The light-weight darkness in Swift you can force.

1: Build option kinda Night/Day/System Mode or On/Off/System Dark Mode.
2: Be awared of Dark Mode changes if you need.

PDM is a single author and personale solution developed in P2P relationship paradigm.

Integration Capabilities

Standalone Swift Package Manager compatible

Dependencies

ConsolePerseusLogger

Support Code

Standalone License

PDMSupportingStar.swift is a peace of code a widly helpful in accord with PDM.
PDMSupportingStar.swift goes as an external part of PDM.

Our Terms

CPL stands for Console Perseus Logger.
PGK stands for Perseus Geo Kit.
PDM stands for Perseus Dark Mode.
P2P stands for Person-to-Person.
A3 stands for Apple Apps Approbation.
T3 stands for The Technological Tree.

PDM in Use

In approbation: iOS app macOS app
In business: The Dark Moon

For details: Approbation and A3 Environment / CHANGELOG

Contents

In brief

THE DARKNESS YOU CAN FORCE

iOS window iOS Settings bundle macOS window

Important

Screenshots taken from Approbation Apps iOS and macOS.

Build requirements

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

First-party software

Type Name License
Star ConsolePerseusLogger / 1.6.0 MIT

Third-party software

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

Installation

Step 1: Import the Darkness either with SPM or standalone

Standalone: the single source code file PDMStar.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
}

Points taken into account

License MIT

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. It means that "Sep 01, 2024" is the beginning of 7533.

Other Required License Notices

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

LICENSE for details.

Credits

Balance and Control kept by Mikhail A. Zhigulin
Source Code written by Mikhail A. Zhigulin
Documentation prepared by Mikhail A. Zhigulin
Product Approbation tested by Mikhail A. Zhigulin

Author

© Mikhail A. Zhigulin of Novosibirsk.