Skip to content

Commit

Permalink
Merge pull request #7 from orchidfire/master
Browse files Browse the repository at this point in the history
Tweaks from cursor.so
  • Loading branch information
eonist authored Sep 19, 2023
2 parents e1641a2 + 8a487b9 commit eaf1a12
Show file tree
Hide file tree
Showing 12 changed files with 194 additions and 105 deletions.
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,19 @@

> 🔦 DarkMode made simple
### Install
- SPM: `.package(url: "https://github.com/sentryco/Darkmode.git", .branch("master"))`
## Table of content:
- [DarkMode](#darkmode)
- [Installation](#installation)
- [Examples](#examples)
- [iOS](#example-ios)
- [macOS](#example-macos)
- [Organizing a Theme](#nice-way-to-organize-a-theme-that-has-support-for-dark-and-light-mode)
- [Organizing Colors](#nice-way-to-organize-colors)
- [Resources](#resources)
- [License](#license)

## Installation
To install DarkMode, use Swift Package Manager (SPM) and add the following to your Package.swift file: `.package(url: "https://github.com/sentryco/Darkmode.git", .branch("master"))`

### Example (iOS)

Expand Down Expand Up @@ -88,3 +99,7 @@ extension Color {

## Resources:
- Good post on darkmode: https://medium.com/@YSaddiq/supporting-dark-mode-in-your-ios-app-3f19b2b12827

## License

This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
38 changes: 22 additions & 16 deletions Sources/DarkMode/Color+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,66 @@ import QuartzCore

extension Color {
/**
* Creates a color object that generates its color data dynamically using the specified colors. For early SDKs creates light color.
* - Fixme: ⚠️️ Why are we creating new colors from `CGColor`? I guess to aboid some strong reference bug etc?
* Creates a color object that generates its color data dynamically using the specified colors. For early SDKs, a light color is created.

Check warning on line 6 in Sources/DarkMode/Color+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 120 characters or less; currently it has 140 characters (line_length)
* - Note: New colors are created from `CGColor` to avoid potential strong reference bugs.
* - Parameters:
* - light: The color for light-mode
* - dark: The color for dark-mode
* - light: The color to be used in light-mode
* - dark: The color to be used in dark-mode
*/
public convenience init(light: Color, dark: Color) {
#if os(macOS)
if #available(macOS 10.15, *) {
self.init(name: nil) { _ in // $0.name which gives: .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark etc
// For macOS 10.15 and later, the color is determined based on the current appearance mode
self.init(name: nil) { _ in
Apperance().inDarkMode ? dark : light
}
} else {
self.init(cgColor: light.cgColor)! // Fallback on earlier versions
// For earlier versions of macOS, light color is used as a fallback
self.init(cgColor: light.cgColor)!
}
#else
if #available(iOS 13.0, tvOS 13.0, *) {
// For iOS 13.0 and tvOS 13.0 and later, the color is determined based on the user interface style
self.init { traitCollection in
traitCollection.userInterfaceStyle == .dark ? dark : light
}
} else {
Swift.print("Fallback on earlier versions")
self.init(cgColor: light.cgColor) // Fallback on earlier versions
// For earlier versions, light color is used as a fallback
self.init(cgColor: light.cgColor)
}
#endif
}
}

/**
* Helpers
* Helper extensions for Color
*/
extension Color {
/**
* Setup custom colors we can use throughout the app using hex values
*/
internal static let youtubeRed = Color(hex: 0xf80000)
internal static let transparentBlack = Color(hex: 0x000000, a: 0.5)

/**
* Create a UIColor from RGB
* - Parameters:
* - red: 0-255
* - green: - 0-255
* - blue: 0-255
* - a: 0-1
* - red: Red component (0-255)
* - green: Green component (0-255)
* - blue: Blue component (0-255)
* - a: Alpha component (0-1)
*/
public convenience init(red: Int, green: Int, blue: Int, a: CGFloat = 1.0) {
self.init(red: .init(red) / 255.0, green: .init(green) / 255.0, blue: .init(blue) / 255.0, alpha: a)
}

/**
* Create a UIColor from a hex value (E.g 0x000000)
* - Parameters:
* - hex: 0x000000
* - a: 0-1
* - hex: Hexadecimal color value
* - a: Alpha component (0-1)
*/
public convenience init(hex: Int, a: CGFloat = 1.0) {
self.init( red: (hex >> 16) & 0xFF, green: (hex >> 8) & 0xFF, blue: hex & 0xFF, a: a)
}
}
}

Check warning on line 68 in Sources/DarkMode/Color+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Newline Violation: Files should have a single trailing newline (trailing_newline)
46 changes: 27 additions & 19 deletions Sources/DarkMode/UIApplication+Extension.swift
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
#if os(iOS)
import UIKit
#endif

extension UIApplication {
/**
* Overrides the user interface style adopted by all windows in all connected scenes.
* - Parameter userInterfaceStyle: The user interface style adopted by all windows in all connected scenes.
* ## Examples:
* let mode: UIUserInterfaceStyle = self.isDarkMode ? .light : .dark
* UIApplication.shared.override(mode)
*/
public func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
if #available(iOS 13.0, *), supportsMultipleScenes {
connectedScenes.forEach { connectedScene in
if let scene = connectedScene as? UIWindowScene {
scene.windows.override(userInterfaceStyle)
/**
* This function allows you to override the user interface style for all windows in all connected scenes.
* This can be useful for implementing features like a 'Dark Mode' toggle.
*
* - Parameter userInterfaceStyle: The desired user interface style. This can be either .light or .dark.
*
* ## Usage Example:
* Determine the current mode, then switch to the opposite mode:
* ```
* let currentMode: UIUserInterfaceStyle = self.isDarkMode ? .light : .dark
* UIApplication.shared.override(currentMode)
* ```
*/
public func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
if #available(iOS 13.0, *), supportsMultipleScenes {
connectedScenes.forEach { connectedScene in
// Check if the connected scene is a UIWindowScene, then override its style
if let scene = connectedScene as? UIWindowScene {
scene.windows.override(userInterfaceStyle)
}
}
}
} else {
windows.override(userInterfaceStyle)
}
}
}
#endif
} else {
// If the iOS version is less than 13.0 or does not support multiple scenes, override the style for all windows
windows.override(userInterfaceStyle)
}
}
}
42 changes: 25 additions & 17 deletions Sources/DarkMode/UIImageAsset+Extension.swift
Original file line number Diff line number Diff line change
@@ -1,48 +1,56 @@
#if os(iOS)
import UIKit

/**
* ## Examples:
* let imageAsset = UIImageAsset(lightModeImage: .init(named: "lightLogo"), darkModeImage: .init(named: "darkLogo"))
* This UIImageAsset extension provides convenience methods for registering and retrieving images based on user interface style.

Check warning on line 5 in Sources/DarkMode/UIImageAsset+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 120 characters or less; currently it has 128 characters (line_length)
* It simplifies the process of managing different images for light and dark modes.
*
* ## Usage:
* let imageAsset = UIImageAsset(lightModeImage: UIImage(named: "lightLogo"), darkModeImage: UIImage(named: "darkLogo"))
*/
extension UIImageAsset {
/**
* Creates an image asset with registration of tht eimages with the light and dark trait collections.
* Convenience initializer for creating an image asset and registering images for light and dark user interface styles.

Check warning on line 13 in Sources/DarkMode/UIImageAsset+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Line Length Violation: Line should be 120 characters or less; currently it has 122 characters (line_length)
* - Parameters:
* - lightModeImage: The image you want to register with the image asset with light user interface style.
* - darkModeImage: The image you want to register with the image asset with dark user interface style.
* - lightModeImage: The image to be used in light user interface style.
* - darkModeImage: The image to be used in dark user interface style.
*/
public convenience init(lightModeImage: UIImage?, darkModeImage: UIImage?) {
self.init()
register(lightModeImage: lightModeImage, darkModeImage: darkModeImage)
}

Check warning on line 22 in Sources/DarkMode/UIImageAsset+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
/**
* Register an images with the light and dark trait collections respectively.
*- Parameters:
* - lightModeImage: The image you want to register with the image asset with light user interface style.
* - darkModeImage: The image you want to register with the image asset with dark user interface style.
* Method to register images for light and dark user interface styles.
* - Parameters:
* - lightModeImage: The image to be used in light user interface style.
* - darkModeImage: The image to be used in dark user interface style.
*/
public func register(lightModeImage: UIImage?, darkModeImage: UIImage?) {
register(lightModeImage, for: .light)
register(darkModeImage, for: .dark)
}

Check warning on line 33 in Sources/DarkMode/UIImageAsset+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
/**
* Register an image with the specified trait collection.
* Method to register an image for a specific user interface style.
* - Parameters:
* - image: The image you want to register with the image asset.
* - traitCollection: The traits to associate with image.
* - image: The image to be registered with the image asset.
* - traitCollection: The user interface style to associate with the image.
*/
public func register(_ image: UIImage?, for userInterfaceStyle: UIUserInterfaceStyle) {
guard let image = image else { return }
register(image, with: .init(userInterfaceStyle: userInterfaceStyle))
register(image, with: UITraitCollection(userInterfaceStyle: userInterfaceStyle))
}

Check warning on line 44 in Sources/DarkMode/UIImageAsset+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
/**
* Returns the variant of the image that best matches the current trait collection. For early SDKs returns the image for light user interface style.
* Method to retrieve the image variant that best matches the current trait collection.
* For early SDKs, it returns the image for light user interface style.
*/
public func image() -> UIImage {
if #available(iOS 13.0, tvOS 13.0, *) {
image(with: .current)
return image(with: UITraitCollection.current)
}
return image(with: .init(userInterfaceStyle: .light))
return image(with: UITraitCollection(userInterfaceStyle: .light))
}
}
#endif
#endif

Check warning on line 56 in Sources/DarkMode/UIImageAsset+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Newline Violation: Files should have a single trailing newline (trailing_newline)
18 changes: 12 additions & 6 deletions Sources/DarkMode/UITraitCollection+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@ import UIKit

extension UITraitCollection {
/**
* A trait collection containing only the light user interface style trait
* This trait collection represents the light user interface style.
* It can be used to apply light-themed styles to your UI components.
*/
public static let light: UITraitCollection = .init(userInterfaceStyle: .light)

/**
* A trait collection containing only the dark user interface style trait
* This trait collection represents the dark user interface style.
* It can be used to apply dark-themed styles to your UI components.
*/
public static let dark: UITraitCollection = .init(userInterfaceStyle: .dark)

/**
* Calls the passed closure only if iOS 13 or tvOS 13 SDKs are available
* Executes the provided closure if the current trait collection differs from the provided one in terms of color appearance.
* This method is only available on iOS 13 or tvOS 13 and later.
*
* - Parameters:
* - traitCollection: A trait collection that you want to compare to the current trait collection.
* - closure: The closure for updating component appearance.
* - traitCollection: The trait collection to compare with the current one.
* - closure: A closure that gets executed if the color appearances of the current and provided trait collections differ.
*/
public func performForDifferentColorAppearance(comparedTo traitCollection: UITraitCollection?, closure: (() -> Void)) {
if #available(iOS 13.0, tvOS 13.0, *), hasDifferentColorAppearance(comparedTo: traitCollection) {
closure()
}
}
}
#endif
#endif
9 changes: 7 additions & 2 deletions Sources/DarkMode/UIViewController+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ import UIKit

extension UIViewController {
/**
* Assert if dark-mode is enabled
* This computed property checks if the dark mode is currently enabled on the device.
* It uses the `userInterfaceStyle` property of the `traitCollection` to determine the interface style.
* If the `userInterfaceStyle` is `.dark`, it means dark mode is enabled and the property returns `true`.
* If the `userInterfaceStyle` is not `.dark` or the iOS version is less than 13.0 (which doesn't support `userInterfaceStyle`), the property returns `false`.
*
* - Returns: A Boolean value indicating whether dark mode is enabled. Returns `true` if dark mode is enabled, `false` otherwise.
*/
public var isDarkMode: Bool {
if #available(iOS 13.0, *) {
Expand All @@ -13,4 +18,4 @@ extension UIViewController {
}
}
}
#endif
#endif
10 changes: 5 additions & 5 deletions Sources/DarkMode/UIWindow+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import UIKit

extension UIWindow {
/**
* Overrides the user interface style adopted by the view and all of its subviews.
* - Parameter userInterfaceStyle: The user interface style adopted by the view and all of its subviews.
* This function allows you to override the user interface style for the current view and all its subviews.
* - Parameter userInterfaceStyle: The desired user interface style to be applied to the view and all its subviews.
*/
public func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
if #available(iOS 13.0, tvOS 13.0, *) {
Expand All @@ -14,13 +14,13 @@ extension UIWindow {
}
extension Array where Element: UIWindow {
/**
* Overrides the user interface style adopted by all elements.
* - Parameter userInterfaceStyle: The user interface style adopted by all elements.
* This function allows you to override the user interface style for all UIWindow elements in the array.
* - Parameter userInterfaceStyle: The desired user interface style to be applied to all UIWindow elements.
*/
public func override(_ userInterfaceStyle: UIUserInterfaceStyle) {
self.forEach { window in
window.override(userInterfaceStyle)
}
}
}
#endif
#endif
14 changes: 10 additions & 4 deletions Sources/DarkMode/UserDefault+Extension.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
#if os(iOS)
import UIKit
#endif

extension UserDefaults {
/**
* Save overriden user interface style
* This computed property allows you to save and retrieve the user's preferred UI style.
* It uses the UserDefaults system to persist this preference across app launches.
* If no preference has been set, it defaults to 'unspecified'.
*/
public var overridedUserInterfaceStyle: UIUserInterfaceStyle {
get {
// Fetch the raw value of the UI style from UserDefaults using the function name as the key.
// If no value is found, default to 'unspecified'.
UIUserInterfaceStyle(rawValue: integer(forKey: #function)) ?? .unspecified
} set {
}

Check warning on line 16 in Sources/DarkMode/UserDefault+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Whitespace Violation: Lines should not have trailing whitespace (trailing_whitespace)
set {
// Save the raw value of the new UI style to UserDefaults using the function name as the key.
set(newValue.rawValue, forKey: #function)
}
}
}
#endif
}

Check warning on line 22 in Sources/DarkMode/UserDefault+Extension.swift

View workflow job for this annotation

GitHub Actions / SwiftLint

Trailing Newline Violation: Files should have a single trailing newline (trailing_newline)
Loading

0 comments on commit eaf1a12

Please sign in to comment.