├── .gitignore ├── .spi.yml ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── README ├── Example.GIF ├── Non-Night Mode Menu.jpeg └── Standby_NightMode.png ├── Sources ├── PlusNightMode │ ├── ColorScheme+.swift │ ├── ColorSchemeMode+.swift │ ├── ColorSchemeMode.swift │ ├── ColorSchemeModeModifier.swift │ ├── Documentation.docc │ │ └── Getting Started.md │ ├── ExampleNightModeView.swift │ ├── Resources │ │ └── Assets.xcassets │ │ │ ├── Contents.json │ │ │ └── blindingWhite.imageset │ │ │ ├── Contents.json │ │ │ └── blindingWhiteImage.jpg │ ├── View + Night Mode.swift │ └── monochromed.swift └── ShouldDifferentiateWithoutColor.swift └── Tests └── Plus Night ModeTests ├── PlusNightModeTests.swift └── __Snapshots__ └── PlusNightModeTests ├── testSnapshot_iOS.Auto-Mode.txt ├── testSnapshot_iOS.Dark-Mode.txt ├── testSnapshot_iOS.Light-Mode.txt └── testSnapshot_iOS.Night-Mode.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | /Derived 10 | /PlusNightMode.xcodeproj 11 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [PlusNightMode] 5 | custom_documentation_parameters: [] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daniel Kalani Lyons 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "originHash" : "ad78593b472bc4772b7ec8a0b780098c3598cbbd5afa4ddfde6797a6720a71f8", 3 | "pins" : [ 4 | { 5 | "identity" : "swift-snapshot-testing", 6 | "kind" : "remoteSourceControl", 7 | "location" : "https://github.com/pointfreeco/swift-snapshot-testing.git", 8 | "state" : { 9 | "revision" : "6d932a79e7173b275b96c600c86c603cf84f153c", 10 | "version" : "1.17.4" 11 | } 12 | }, 13 | { 14 | "identity" : "swift-syntax", 15 | "kind" : "remoteSourceControl", 16 | "location" : "https://github.com/swiftlang/swift-syntax", 17 | "state" : { 18 | "revision" : "06b5cdc432e93b60e3bdf53aff2857c6b312991a", 19 | "version" : "600.0.0-prerelease-2024-07-30" 20 | } 21 | } 22 | ], 23 | "version" : 3 24 | } 25 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.10.0 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "PlusNightMode", 8 | platforms: [.iOS(.v16), .macOS(.v13), .watchOS(.v9), .visionOS(.v1), .tvOS(.v16), .macCatalyst(.v16)], 9 | products: [ 10 | // Products define the executables and libraries a package produces, making them visible to other packages. 11 | .library( 12 | name: "PlusNightMode", 13 | targets: ["PlusNightMode"]), 14 | ], 15 | dependencies: [ 16 | .package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.17.3"), 17 | // .package(url: "https://github.com/swiftlang/swift-docc.git", branch: "main"), 18 | ], 19 | targets: [ 20 | // Targets are the basic building blocks of a package, defining a module or a test suite. 21 | // Targets can depend on other targets in this package and products from dependencies. 22 | .target( 23 | name: "PlusNightMode", 24 | dependencies: [ 25 | // .product(name: "SwiftDocC", package: "swift-docc"), 26 | ], 27 | resources: [ 28 | .process("Resources/"), 29 | ], 30 | swiftSettings: [.enableExperimentalFeature("StrictConcurrency")] 31 | ), 32 | .testTarget( 33 | name: "PlusNightModeTests", 34 | dependencies: [ 35 | "PlusNightMode", 36 | .product(name: "SnapshotTesting", package: "swift-snapshot-testing"), 37 | .product(name: "InlineSnapshotTesting", package: "swift-snapshot-testing") 38 | ]), 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PlusNightMode 2 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FDandyLyons%2FPlusNightMode%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/DandyLyons/PlusNightMode) 3 | [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FDandyLyons%2FPlusNightMode%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/DandyLyons/PlusNightMode) 4 | 5 | PlusNightMode makes it easy for your app to add user-configurable light mode, dark mode, automatic (OS Setting), and night mode. It is designed to work with SwiftUI. 6 | 7 | 8 | 9 | ## Sections 10 | - [PlusNightMode](#plusnightmode) 11 | - [Sections](#sections) 12 | - [What is night mode?](#what-is-night-mode) 13 | - [Usage](#usage) 14 | - [ColorSchemeMode](#colorschememode) 15 | - [Known Limitations](#known-limitations) 16 | - [Design Considerations](#design-considerations) 17 | - [Collaboration](#collaboration) 18 | - [Thank Yous](#thank-yous) 19 | 20 | ## What is night mode? 21 | 22 | It has long been known that exposure to light at night decreases our bodies' production of melatonin. In WWDC 23, Apple unveiled an innovative approach to tackling this problem, "Night Mode". It's a UI visual appearance that is "red-scale", meaning every single pixel on the screen is either black, or a shade of red. This is valuable for 2 main reasons. Overall less light is shown. Second, blue light (which has the most negative impact on melatonin production) is filtered out. However, currently, Apple's Night Mode is only available iOS 17's StandBy, and one single watch face. Now you can add night mode to your app with a few lines of code. 23 | 24 | In other words, Night Mode protects your users from health hazardous, sleep-depriving light. 25 | 26 | ## Usage 27 | Simply add `.observingNightMode()` to the very top of your View hierarchy like so: 28 | 29 | ```swift 30 | struct NightModeView: View { 31 | @Environment(\.colorSchemeMode) var colorSchemeMode // 👈🏼 32 | 33 | var body: some View { 34 | NavigationStack { 35 | List { 36 | Image(.blindingWhite) 37 | .resizable() 38 | .frame(maxWidth: .infinity) 39 | .aspectRatio(1.0, contentMode: .fill) 40 | Text("This is a text view") 41 | Text("Blue").foregroundStyle(.blue) 42 | Text("Green").foregroundStyle(.green) 43 | Text("Yellow").foregroundStyle(.yellow) 44 | NavigationLink("Go to second page", value: "second page") 45 | } 46 | .navigationTitle("Hello World!") 47 | .navigationDestination(for: String.self) { string in 48 | Text(string) 49 | } 50 | } 51 | .colorSchemeMode($colorSchemeMode) // 👈🏼 52 | } 53 | } 54 | ``` 55 | This will turn that View and all of it's child views into night mode. 56 | 57 | Please note, presented views are not considered to be child views by SwiftUI. Therefore whenever you present a View (for example in a `.sheet`) you must apply `.observingNightMode()` to that presented View as well. 58 | 59 | ```swift 60 | struct ExampleView: View { 61 | @Environment(\.colorSchemeMode) var colorSchemeMode 62 | 63 | var body: some View { 64 | Button { 65 | isPresenting.toggle() 66 | } label: { 67 | Text("Present Sheet View") 68 | } 69 | .colorSchemeMode($colorSchemeMode) 70 | .sheet(isPresented: $isPresenting) { 71 | Text("Presented View") 72 | .colorSchemeMode($colorSchemeMode) 73 | // 👆🏼 SwiftUI will not add Night Mode to 74 | // presented views unless you explicitly add it 75 | } 76 | } 77 | } 78 | ``` 79 | 🚀 But there's an even better way to add night mode, that even responds to the device's current dark mode settings. It's called **ColorSchemeMode**. 80 | 81 | ### ColorSchemeMode 82 | 83 | PlusNightMode also comes with `ColorSchemeMode`, a simple wrapper struct that adds some functionality to SwiftUI's [ColorScheme](https://developer.apple.com/documentation/swiftui/colorscheme), including night mode. To use, simply create and store a `ColorSchemeMode` somewhere in your model. Then set `.colorSchemeMode()` at the top of your View hierarchy. 84 | 85 | ```swift 86 | struct ExampleView: View { 87 | @State var colorSchemeMode: ColorSchemeMode = .night 88 | 89 | 90 | var body: some View { 91 | Text("Hello world") 92 | .colorSchemeMode($colorSchemeMode) 93 | } 94 | } 95 | ``` 96 | 97 | `ColorSchemeMode` can have the following values: 98 | - `night`: A monochrome red on black presentation 99 | - `dark`: Dark mode 100 | - `light`: Light mode 101 | - `auto`: Automatically adjust to the device's current light/dark mode setting. 102 | 103 | For a user-configurable `ColorSchemeMode` that you would like to keep in sync across the whole app, try using the EnvironmentValue: 104 | ```swift 105 | @Environment(\.colorSchemeMode) var colorSchemeMode 106 | // ... 107 | MyView() 108 | .colorSchemeMode($colorSchemeMode) 109 | ``` 110 | 111 | For a `ColorSchemeMode` that can be also persisted even after the app is closed try using `@AppStorage`: 112 | ```swift 113 | @AppStorage("colorSchemeMode") var colorSchemeMode 114 | 115 | MyView() 116 | .colorSchemeMode($colorSchemeMode) 117 | ``` 118 | 119 | 120 | ## Known Limitations 121 | 122 | We can only apply night mode to views within the SwiftUI View hierarchy. This does not include system views such as the status bar at the top of the screen. 123 | 124 | Certain SwiftUI Views cannot be styled or overlayed. For example, when a user taps a SwiftUI `Menu`. Notice how the presented menu is in dark mode, not in night mode. If you find a workaround please open an Issue or PR. 125 | 126 | 127 | 128 | ## Design Considerations 129 | 130 | Be sure to test your design in all use cases. Some things to look out for: 131 | 132 | - Night Mode will of course filter out blue light (that's the whole point of it). For this reason, blue elements can become invisible or difficult to see. 133 | - Since Night Mode is monochrome, your UI cannot use color to communicate to the user. Therefore, it's recommended to: 134 | - Add the SwiftUI environment value [accessibilityDifferentiateWithoutColor](https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilitydifferentiatewithoutcolor) to tell all child views when they need to use shapes, rather than colors to communicate to the user. (In an upcoming release PlusNightMode will handle this automatically.) 135 | - Add simple logic for child views to respect accessibilityDifferentiateWithoutColor. *Hacking with Swift* has a very helpful [tutorial](https://www.hackingwithswift.com/books/ios-swiftui/supporting-specific-accessibility-needs-with-swiftui) on this subject. 136 | 137 | ## Collaboration 138 | 139 | Please feel free to open a PR. 140 | 141 | ## Thank Yous 142 | - [Swift Package Index](https://swiftpackageindex.com/) for mentioning us in their [podcast episode](https://podcasts.apple.com/us/podcast/39-stress-testing-dependency-management/id1654567329?i=1000641328907) (at 26:00). 143 | -------------------------------------------------------------------------------- /README/Example.GIF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DandyLyons/PlusNightMode/b77d7c7f3e7fccaa4bbfe3ff0e7aa072c0564803/README/Example.GIF -------------------------------------------------------------------------------- /README/Non-Night Mode Menu.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DandyLyons/PlusNightMode/b77d7c7f3e7fccaa4bbfe3ff0e7aa072c0564803/README/Non-Night Mode Menu.jpeg -------------------------------------------------------------------------------- /README/Standby_NightMode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DandyLyons/PlusNightMode/b77d7c7f3e7fccaa4bbfe3ff0e7aa072c0564803/README/Standby_NightMode.png -------------------------------------------------------------------------------- /Sources/PlusNightMode/ColorScheme+.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension ColorScheme: CustomDebugStringConvertible { 4 | public var debugDescription: String { 5 | switch self { 6 | case .light: ".light" 7 | case .dark: ".dark" 8 | @unknown default: "@unknown default" 9 | } 10 | } 11 | } 12 | 13 | public extension ColorScheme { 14 | var opposite: Self { 15 | switch self { 16 | case .light: .dark 17 | case .dark: .light 18 | @unknown default: .dark 19 | } 20 | } 21 | } 22 | 23 | public extension View { 24 | /// A convenience `View` that will override the light and dark appearance according to the provided `ColorScheme` 25 | /// - Parameters: 26 | /// - colorScheme: The SwiftUI `ColorScheme` 27 | /// - light: The View to display when `colorScheme` is `.light` 28 | /// - dark: The View to display when `colorScheme` is `.dark` 29 | /// - Returns: The Light and Dark View 30 | @ViewBuilder 31 | func view(for colorScheme: ColorScheme, light: () -> L, dark: () -> D) -> some View { 32 | switch colorScheme { 33 | case .light: light() 34 | case .dark: dark() 35 | @unknown default: dark() 36 | } 37 | } 38 | 39 | /// A convenience `View` that will override the light and dark appearance. 40 | /// - Parameters: 41 | /// - colorScheme: The SwiftUI `ColorScheme` 42 | /// - light: The View to display when `colorScheme` is `.light` 43 | /// - dark: The View to display when `colorScheme` is `.dark` 44 | /// - Returns: The Light and Dark View 45 | @ViewBuilder 46 | func view(@ViewBuilder light: () -> L, @ViewBuilder dark: () -> D) -> some View { 47 | @Environment(\.colorScheme) var colorScheme 48 | switch colorScheme { 49 | case .light: light() 50 | case .dark: dark() 51 | @unknown default: dark() 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/PlusNightMode/ColorSchemeMode+.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SwiftUI 3 | 4 | // MARK: EnvironmenValues 5 | extension EnvironmentValues { 6 | public var colorSchemeMode: Binding { 7 | get { self[ColorSchemeModeBindingKey.self] } 8 | set { self[ColorSchemeModeBindingKey.self] = newValue } 9 | } 10 | } 11 | 12 | public struct ColorSchemeModeBindingKey: EnvironmentKey { 13 | public static var defaultValue: Binding { 14 | .constant(.auto) 15 | } 16 | } 17 | 18 | // MARK: CustomDebugStringConvertible 19 | extension ColorSchemeMode: CustomDebugStringConvertible { 20 | public var debugDescription: String { 21 | return self.value.rawValue 22 | } 23 | } 24 | 25 | // MARK: CaseIterable 26 | extension ColorSchemeMode: CaseIterable { 27 | public static var allCases: [ColorSchemeMode] { 28 | [.auto, .dark, .light, .night] 29 | } 30 | 31 | public static let auto: Self = .init(value: .auto) 32 | public static let light: Self = .init(value: .light) 33 | public static let dark: Self = .init(value: .dark) 34 | public static let night: Self = .init(value: .night) 35 | } 36 | 37 | // MARK: Codable 38 | extension ColorSchemeMode: Codable { 39 | public enum CodingKeys: CodingKey { case value } 40 | 41 | public init(from decoder: Decoder) throws { 42 | 43 | let container = try decoder.container(keyedBy: CodingKeys.self) 44 | 45 | let decodedValue = try container.decode(Value.self, forKey: .value) 46 | 47 | self.init(value: decodedValue) 48 | } 49 | 50 | public func encode(to encoder: Encoder) throws { 51 | var container = encoder.container(keyedBy: CodingKeys.self) 52 | try container.encode(value, forKey: .value) 53 | } 54 | } 55 | 56 | // MARK: Equatable, Hashable 57 | extension ColorSchemeMode: Equatable, Hashable { 58 | public static func == (lhs: ColorSchemeMode, rhs: ColorSchemeMode) -> Bool { 59 | lhs.value == rhs.value 60 | } 61 | 62 | public func hash(into hasher: inout Hasher) { 63 | hasher.combine(value) 64 | } 65 | } 66 | 67 | // MARK: Identifiable 68 | extension ColorSchemeMode: Identifiable { 69 | public var id: String { self.value.rawValue } 70 | } 71 | 72 | 73 | // MARK: LocalizedStringKey 74 | extension ColorSchemeMode { 75 | public var localizedString: LocalizedStringKey { 76 | return LocalizedStringKey(self.value.rawValue) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/PlusNightMode/ColorSchemeMode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Daniel Lyons on 9/22/23. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | /// A wrapper type that provides extra functionality to SwiftUI's `ColorScheme` 12 | /// 13 | /// To get the underlying `ColorScheme` value, use `resolvedColorScheme` 14 | public struct ColorSchemeMode: Sendable { 15 | public var value: Value 16 | 17 | 18 | public var resolvedColorScheme: ColorScheme? { 19 | return value.resolvedColorScheme 20 | } 21 | 22 | public init(value: Value) { 23 | self.value = value 24 | } 25 | 26 | /// The underlying value of the ``ColorSchemeMode``. 27 | public enum Value: String, CaseIterable, Hashable, Codable, Identifiable, Sendable { 28 | /// Light Mode 29 | case light = "Light" 30 | /// Dark Mode 31 | case dark = "Dark" 32 | /// A View presentation designed to minimize the negative impact of harmful sleep-depriving blue light. 33 | /// This appearance effectively makes it so that every pixel is either pitch black or a shade of red. 34 | /// Note: Night Mode is not built into the system. Instead, you can observe the `\.colorSchemeMode` 35 | /// EnvironmentKey and respond to it using `observingNightMode()` or `colorSchemeMode(_:)` 36 | case night = "Night" 37 | /// In this mode, the App will simply observe the Dark/Light mode setting of the device. 38 | case auto = "Automatic" 39 | 40 | public var id: String { self.rawValue } 41 | 42 | public init(rawValue: String) { 43 | switch rawValue { 44 | case Self.light.rawValue: self = .light 45 | case Self.dark.rawValue: self = .dark 46 | case Self.night.rawValue: self = .night 47 | case Self.auto.rawValue: self = .auto 48 | default: self = .auto 49 | } 50 | } 51 | 52 | public var localizedString: LocalizedStringKey { 53 | return LocalizedStringKey(self.rawValue) 54 | } 55 | 56 | /// for use in `View.preferredColorScheme` 57 | public var resolvedColorScheme: SwiftUI.ColorScheme? { 58 | switch self { 59 | case .light: return .light 60 | case .dark: return .dark 61 | case .night: return .dark 62 | case .auto: return nil 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sources/PlusNightMode/ColorSchemeModeModifier.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct ColorSchemeModeEnvironmentKeyModifier: ViewModifier { 4 | @Binding var mode: ColorSchemeMode 5 | 6 | public func body(content: Content) -> some View { 7 | content 8 | .preferredColorScheme(mode.value.resolvedColorScheme) 9 | .observingNightMode($mode) 10 | } 11 | } 12 | 13 | extension View { 14 | public func colorSchemeMode(_ mode: Binding) -> some View { 15 | self.modifier(ColorSchemeModeEnvironmentKeyModifier(mode: mode)) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Sources/PlusNightMode/Documentation.docc/Getting Started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | 4 | ``PlusNightMode`` makes it easy to add dynamic, user-configurable appearance controls to any SwiftUI View. 5 | 6 | 7 | ``PlusNightMode`` supports 4 appearance settings: 8 | 1. ``ColorSchemeMode.Value.light``: A light mode appearance 9 | 2. ``ColorSchemeMode.Value.dark``: A dark mode appearance 10 | 3. ``ColorSchemeMode.Value.auto``: An appearance that will automatically switch between light and dark appearance according to the parent View's color scheme. (If every ancestor View has a color scheme that has not been overridden, then this appearance will follow the appearance of the device.) 11 | 4. ``ColorSchemeMode.Value.night``: A night mode appearance 12 | 13 | ## What's Night Mode 14 | Night Mode is an appearance designed to aggressively reduce the amount of harmful, sleep-depriving blue light. It does this by a combination of these strategies: 15 | 1. Under the hood, the night mode appearance will give every child SwiftUI View a dark SwiftUI color scheme. 16 | 2. Then the Night Mode appearance will apply a red color multiply filter over the View. 17 | 18 | This effectively means that every pixel of View will be pitch black or a shade of red! 19 | 20 | ## `ColorScheme` vs. `ColorSchemeMode` 21 | - SwiftUI has a type called [ColorScheme](https://developer.apple.com/documentation/swiftui/colorscheme): 22 | - This only has support for light and dark mode. 23 | - There's no support for auto or night. 24 | - PlusNightMode introduces a new type called ``ColorSchemeMode``: 25 | - It adds support for auto and night appearance. 26 | 27 | ## Adding Night Mode 28 | The easiest way to add night mode is to use the ``nightModed(if:)`` method and pass it a value of `true`. This method will force the View (and all its children to have a night mode appearance). (However, it will pay no attention to users settings in the app, nor in device settings.) 29 | 30 | ```swift 31 | @State var shouldNightMode = true 32 | // ... 33 | MyView() 34 | .nightModed(if: shouldNightMode) 35 | ``` 36 | 37 | But the best way to add night mode to your View is to use ``colorSchemeMode(_:)``. This will ensure that the SwiftUI View (and all its children) will dynamically update to match the user's current settings both in the app and in the device settings. 38 | 39 | ```swift 40 | @Environment(\.colorSchemeMode) var colorSchemeMode 41 | // ... 42 | MyView() 43 | .colorSchemeMode($colorSchemeMode) 44 | ``` 45 | ## Adding User Configurability 46 | Now you can add the color scheme mode to your app's settings like this, and the changes will be visible across the app! 47 | 48 | ```swift 49 | // In your Settings Screen 50 | @Environment(\.colorSchemeMode) var colorSchemeMode 51 | // ... 52 | Picker("ColorSchemeMode", selection: $colorSchemeMode) { 53 | Text("Night").tag(ColorSchemeMode.night) 54 | Text("Dark").tag(ColorSchemeMode.dark) 55 | Text("Light").tag(ColorSchemeMode.light) 56 | Text("Auto").tag(ColorSchemeMode.auto) 57 | } 58 | .pickerStyle(.menu) 59 | ``` 60 | 61 | ## On View Hierarchy 62 | It is important to note that ``colorSchemeMode(_:)`` can only apply the night mode appearance to children views. It cannot apply the night mode appearance to parent or sibling views. Because of this, it is best practice to: 63 | 1. Apply ``colorSchemeMode(_:)`` at the highest possible level of each View hierarchy. The higher the better, since only children will be affected. 64 | 2. Reapply ``colorSchemeMode(_:)`` when creating a branching view hierarchy: 65 | - Certain views such as view presentations, like SwiftUI's `.sheet` create an entirely new View hierarchy. For this you need to reapply ``colorSchemeMode(_:)`` to the presented View. 66 | 67 | ```swift 68 | struct ExampleView: View { 69 | @Environment(\.colorSchemeMode) var colorSchemeMode 70 | 71 | var body: some View { 72 | Button { 73 | isPresenting.toggle() 74 | } label: { 75 | Text("Present Sheet View") 76 | } 77 | .colorSchemeMode($colorSchemeMode) 78 | .sheet(isPresented: $isPresenting) { 79 | Text("Presented View") 80 | .colorSchemeMode($colorSchemeMode) 81 | // 👆🏼 SwiftUI will not add Night Mode to 82 | // presented views unless you explicitly add it 83 | } 84 | } 85 | } 86 | ``` -------------------------------------------------------------------------------- /Sources/PlusNightMode/ExampleNightModeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NightModeView.swift 3 | // 4 | // Created by Daniel Lyons on 9/12/23. 5 | // 6 | 7 | import SwiftUI 8 | import Foundation 9 | 10 | 11 | /// An example `View` to demonstrate how ``colorSchemeMode(_:)`` affects appearance. 12 | /// 13 | /// Also used in tests. 14 | @available(iOS 17.0, macOS 14.0, *) 15 | public struct ExampleNightModeView: View { 16 | public init(colorSchemeMode: ColorSchemeMode = .night) { 17 | self._colorSchemeMode = State(initialValue: colorSchemeMode) 18 | } 19 | 20 | @State public var colorSchemeMode: ColorSchemeMode 21 | @Environment(\.colorScheme) var colorScheme 22 | // @Environment(\.shouldDifferentiateWithoutColor) var shouldDifferentiateWithoutColor 23 | 24 | public var body: some View { 25 | NavigationStack { 26 | List { 27 | // Image("blindingWhite", bundle: .main) 28 | Image(ImageResource(name: "blindingWhite", bundle: .module)) 29 | .resizable() 30 | .frame(maxWidth: .infinity) 31 | .aspectRatio(1.0, contentMode: .fill) 32 | Text("This is a text view") 33 | Text("Blue").foregroundStyle(.blue) 34 | Text("Green").foregroundStyle(.green) 35 | Text("Yellow").foregroundStyle(.yellow) 36 | NavigationLink("Go to Settings", value: "Settings") 37 | 38 | Section { 39 | Picker("ColorSchemeMode", selection: $colorSchemeMode) { 40 | Text("Night").tag(ColorSchemeMode.night) 41 | Text("Dark").tag(ColorSchemeMode.dark) 42 | Text("Light").tag(ColorSchemeMode.light) 43 | Text("Auto").tag(ColorSchemeMode.auto) 44 | } 45 | .pickerStyle(.menu) 46 | 47 | Text("ColorScheme: \(colorScheme.debugDescription)") 48 | Text("ColorSchemeMode: \(colorSchemeMode.debugDescription)") 49 | } 50 | 51 | 52 | } 53 | .navigationTitle("Hello World!") 54 | .navigationDestination(for: String.self) { string in 55 | ExampleSettingsView() 56 | } 57 | } 58 | .colorSchemeMode($colorSchemeMode) 59 | } 60 | } 61 | 62 | /// An example Settings screen `View` to demonstrate how ``colorSchemeMode(_:)`` affects appearance. 63 | /// 64 | /// Also used in tests. 65 | @available(macOS 14.0, iOS 17.0, *) 66 | @MainActor // This shouldn't be necessary post Xcode 16 since `View` will be `@MainActor` isolated 67 | public extension ExampleNightModeView { 68 | static let night = Self(colorSchemeMode: .night) 69 | static let light = Self(colorSchemeMode: .light) 70 | static let auto = Self(colorSchemeMode: .auto) 71 | static let dark = Self(colorSchemeMode: .dark) 72 | } 73 | 74 | 75 | public struct ExampleSettingsView: View { 76 | @Environment(\.colorSchemeMode) private var colorSchemeMode 77 | 78 | @Environment(\.colorScheme) private var colorScheme 79 | @State private var preferredColorScheme: ColorScheme = .light 80 | 81 | public var body: some View { 82 | Form { 83 | Picker("ColorScheme", selection: $preferredColorScheme) { 84 | Text("Dark").tag(ColorScheme.dark) 85 | Text("Light").tag(ColorScheme.light) 86 | } 87 | .pickerStyle(.menu) 88 | 89 | Picker("ColorSchemeMode", selection: colorSchemeMode) { 90 | Text("Night").tag(ColorSchemeMode.night) 91 | Text("Dark").tag(ColorSchemeMode.dark) 92 | Text("Light").tag(ColorSchemeMode.light) 93 | Text("Auto").tag(ColorSchemeMode.auto) 94 | } 95 | .pickerStyle(.menu) 96 | 97 | 98 | Section { 99 | Text("ColorScheme: \(colorScheme.debugDescription)") } 100 | } 101 | .navigationTitle("Settings") 102 | } 103 | } 104 | 105 | @available(macOS 14.0, iOS 17.0, *) 106 | #Preview("Night") { 107 | ExampleNightModeView.night 108 | } 109 | 110 | @available(macOS 14.0, iOS 17.0, *) 111 | #Preview("Light") { 112 | ExampleNightModeView.light 113 | } 114 | 115 | @available(macOS 14.0, iOS 17.0, *) 116 | #Preview("Dark") { 117 | ExampleNightModeView.dark 118 | } 119 | 120 | @available(macOS 14.0, iOS 17.0, *) 121 | #Preview("Auto") { 122 | ExampleNightModeView.auto 123 | } 124 | 125 | -------------------------------------------------------------------------------- /Sources/PlusNightMode/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Sources/PlusNightMode/Resources/Assets.xcassets/blindingWhite.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "idiom" : "universal", 9 | "scale" : "2x" 10 | }, 11 | { 12 | "filename" : "blindingWhiteImage.jpg", 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/PlusNightMode/Resources/Assets.xcassets/blindingWhite.imageset/blindingWhiteImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DandyLyons/PlusNightMode/b77d7c7f3e7fccaa4bbfe3ff0e7aa072c0564803/Sources/PlusNightMode/Resources/Assets.xcassets/blindingWhite.imageset/blindingWhiteImage.jpg -------------------------------------------------------------------------------- /Sources/PlusNightMode/View + Night Mode.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension View { 4 | 5 | 6 | @ViewBuilder 7 | public func observingNightMode(_ bool: Bool = true) -> some View { 8 | nightModed(if: bool) 9 | } 10 | 11 | @ViewBuilder 12 | func observingNightMode(_ colorSchemeMode: Binding) -> some View { 13 | let bool = colorSchemeMode.wrappedValue == .night 14 | 15 | 16 | nightModed(if: bool) 17 | .preferredColorScheme(colorSchemeMode.wrappedValue.resolvedColorScheme) 18 | .environment(\.colorSchemeMode, colorSchemeMode) 19 | // .colorScheme(colorSchemeMode.wrappedValue.value.resolvedColorScheme ?? colorScheme) 20 | } 21 | 22 | public func nightModed(if bool: Bool) -> some View { 23 | monochromed(if: bool, color: .red, colorScheme: .dark) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Sources/PlusNightMode/monochromed.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension View { 4 | 5 | 6 | /// Converts the appearance of a View to a monochrome appearance. 7 | /// 8 | /// Also injects the `shouldDifferentiateWithoutColor` Environment variable 9 | /// so that child views know that they must not use color to communicate to the user. 10 | /// - Parameters: 11 | /// - bool: Whether the View should be monochromed 12 | /// - color: What color the View should be monochromed to 13 | /// - colorScheme: The light/dark mode appearance 14 | /// - Returns: The monochromed View. 15 | public func monochromed(if bool: Bool, color: Color, colorScheme: ColorScheme) -> some View { 16 | 17 | let filter: some View = color 18 | .blendMode(bool ? .color : .normal) 19 | .opacity(bool ? 0.5 : 0.0) 20 | .allowsHitTesting(false) 21 | .ignoresSafeArea() 22 | // .shouldDifferentiateWithoutColor() 23 | 24 | return self 25 | .preferredColorScheme(bool ? colorScheme : nil) 26 | .tint(bool ? color : nil) 27 | .overlay { 28 | filter 29 | } 30 | .colorMultiply(bool ? color : .white) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Sources/ShouldDifferentiateWithoutColor.swift: -------------------------------------------------------------------------------- 1 | 2 | import SwiftUI 3 | 4 | /// A key used to determine whether the View should differentiation without color. 5 | struct ShouldDifferentiateWithoutColorKey: EnvironmentKey { 6 | 7 | static let defaultValue: Bool? = nil 8 | } 9 | 10 | extension EnvironmentValues { 11 | 12 | 13 | /// Communicates to this View whether it should differentiate without color. 14 | /// >Important: You do not set this value. 15 | /// >It will be set for you whenever you use PlusNightMode and your View is monochromed. 16 | /// 17 | /// This Environment value communicates to the View whether it should use color to convey 18 | /// information to the user. 19 | /// ## Purpose 20 | /// Not all users are able to distinguish between colors on screen. 21 | /// Therefore, to improve accessibility, Apple provides the "Differentiate without color" accessibility option in Settings. 22 | /// In order for apps to honor this setting, SwiftUI provides the `accessibilityDifferentiateWithoutColor` EnvironmentValue. 23 | /// 24 | /// PlusNightMode introduces and solves a similar problem. Because, Night Mode is a red on black appearance, it must be monochrome. 25 | /// But if a View is monochrome, then the user loses all ability to differentiate colors, because now the UI only has one color. 26 | /// 27 | /// For this reason, PlusNightMode offers the `shouldDifferentiateWithoutColor` Environment value. 28 | /// This value will factor in both, whether the View is currently monochromed, and the current value of `accessibilityDifferentiateWithoutColor`. 29 | /// ## Listening to whether a View should differentiate without color 30 | /// For best user experience, all of your Views should listen to, and honor the `shouldDifferentiateWithoutColor` Environment value. This way, your Views can know when to use shapes and glyphs rather than color to communicate to users. 31 | /// 32 | /// ## Migrating from accessibilityDifferentiateWithoutColor 33 | /// If you were already listening to `accessibilityDifferentiateWithoutColor` then simply switch to `shouldDifferentiateWithoutColor` like so. 34 | /// ```diff 35 | /// - @Environment(\.accessibilityDifferentiateWithoutColor) var differentiateWithoutColor 36 | /// + @Environment(\.shouldDifferentiateWithoutColor) var differentiateWithoutColor 37 | /// ``` 38 | /// You will still receive the underlying value of `accessibilityDifferentiateWithoutColor`, plus 39 | /// now you'll know when your Views are monochromed by PlusNightMode. All other View logic should be unaffected. 40 | /// 41 | /// >Note: It will evaluate `accessibilityDifferentiateWithoutColor` at the time that 42 | /// >the View is first monochromed. In other words, it will not receive subsequent updates when the user 43 | /// changes "Differentiate without color" in their accessibility settings. However, users are unlikely to change this setting 44 | /// back and forth, and therefore this shouldn't be an issue in practice. 45 | /// 46 | public var shouldDifferentiateWithoutColor: Bool? { 47 | get { self[ShouldDifferentiateWithoutColorKey.self] } 48 | set { self[ShouldDifferentiateWithoutColorKey.self] = newValue } 49 | } 50 | } 51 | 52 | /// A view modifier that adjusts the differentiation without color settings. 53 | @available(*, deprecated, message: "WIP Unfinished") 54 | struct ShouldDifferentiateWithoutColorViewModifier: ViewModifier { 55 | @Environment(\.accessibilityDifferentiateWithoutColor) var accessibilityDifferentiateWithoutColor 56 | @Environment(\.shouldDifferentiateWithoutColor) var shouldDifferentiateWithoutColor 57 | 58 | /// Applies the differentiation without color settings to the content. 59 | /// 60 | /// This modifier assesses `accessibilityDifferentiateWithoutColor` at the time of the Views creation 61 | /// and does not respond to updates. However, users are not likely to change this setting on their device often, 62 | /// and so that should not be a problem. 63 | /// - Parameter content: The content to modify. 64 | /// - Returns: The modified content. 65 | func body(content: Content) -> some View { 66 | let differentiateWithoutColor = accessibilityDifferentiateWithoutColor ? true : shouldDifferentiateWithoutColor 67 | return content 68 | .environment(\.shouldDifferentiateWithoutColor, differentiateWithoutColor) 69 | } 70 | } 71 | 72 | extension View { 73 | 74 | func shouldDifferentiateWithoutColor() -> some View { 75 | self.modifier(ShouldDifferentiateWithoutColorViewModifier()) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Tests/Plus Night ModeTests/PlusNightModeTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PlusNightMode 3 | import SnapshotTesting 4 | 5 | import SwiftUI 6 | 7 | @available(macOS, unavailable) 8 | @available(watchOS, unavailable) 9 | @available(tvOS, unavailable) 10 | final class PlusNightModeTests: XCTestCase { 11 | @MainActor 12 | func testSnapshot_iOS() { 13 | withSnapshotTesting(record: .never, diffTool: .default) { 14 | let view = ExampleNightModeView.auto 15 | var hostingController = UIHostingController(rootView: view) 16 | let screenBounds = UIScreen.main.bounds 17 | hostingController.view.frame = screenBounds 18 | 19 | hostingController.rootView = ExampleNightModeView.night 20 | assertSnapshot( 21 | of: hostingController, 22 | as: .recursiveDescription, 23 | named: "Night Mode" 24 | ) 25 | 26 | hostingController.rootView = ExampleNightModeView.light 27 | assertSnapshot( 28 | of: hostingController, 29 | as: .recursiveDescription, 30 | named: "Light Mode" 31 | ) 32 | 33 | hostingController.rootView = ExampleNightModeView.dark 34 | assertSnapshot( 35 | of: hostingController, 36 | as: .recursiveDescription, 37 | named: "Dark Mode" 38 | ) 39 | 40 | hostingController.rootView = ExampleNightModeView.auto 41 | assertSnapshot( 42 | of: hostingController, 43 | as: .recursiveDescription, 44 | named: "Auto Mode" 45 | ) 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Tests/Plus Night ModeTests/__Snapshots__/PlusNightModeTests/testSnapshot_iOS.Auto-Mode.txt: -------------------------------------------------------------------------------- 1 | <_TtGC7SwiftUI14_UIHostingViewV13PlusNightMode20ExampleNightModeView_; frame = (0 0; 393 852); autoresize = W+H; gestureRecognizers = ; backgroundColor = ; layer = > 2 | | <_TtC7SwiftUIP33_A34643117F00277B93DEBAB70EC0697116_UIInheritedView; frame = (0 0; 393 852); anchorPoint = (0, 0); autoresizesSubviews = NO; layer = > 3 | | | <_TtGC7SwiftUI16PlatformViewHostGVS_42PlatformViewControllerRepresentableAdaptorVS_P10$1c5547df828NavigationStackRepresentable__; baseClass = _UIConstraintBasedLayoutHostingView; frame = (0 0; 393 852); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 1 0.231373 0.188235 1; layer = > 4 | | | | ; layer = > 5 | | | | | > 6 | | | | | | > 7 | | | | | | | <_TtGC7SwiftUI14_UIHostingViewVS_7AnyView_; frame = (0 0; 393 852); clipsToBounds = YES; gestureRecognizers = ; backgroundColor = ; layer = > 8 | | | | | | | | <_TtGC7SwiftUI16PlatformViewHostGVS_P10$1c554ec9c17ListRepresentableGVS_28CollectionViewListDataSourceOs5Never_GOS_19SelectionManagerBoxS3____; baseClass = _UIConstraintBasedLayoutHostingView; frame = (0 0; 393 852); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 1 0.231373 0.188235 1; layer = > 9 | | | | | | | | | ; backgroundColor = ; layer = ; contentOffset: {0, -96}; contentSize: {393, 742.33333333333326}; adjustedContentInset: {96, 0, 0, 0}; layout: ; dataSource: <_TtGC7SwiftUI31UICollectionViewListCoordinatorGVS_28CollectionViewListDataSourceOs5Never_GOS_19SelectionManagerBoxS2___>> 10 | | | | | | | | | | <_UICollectionViewListLayoutSectionBackgroundColorDecorationView; frame = (-20 -1704; 433 2276.67); userInteractionEnabled = NO; backgroundColor = ; layer = > 11 | | | | | | | | | | <_UICollectionViewListLayoutSectionBackgroundColorDecorationView; frame = (-20 572.667; 433 1873.67); userInteractionEnabled = NO; backgroundColor = ; layer = > 12 | | | | | | | | | | > 13 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 335); layer = ; configuration = >> 14 | | | | | | | | | | | | ; layer = > 15 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 335); gestureRecognizers = ; layer = > 16 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 335); autoresize = W+H; gestureRecognizers = ; layer = > 17 | | | | | | | | | | | | | > 18 | | | | | | | | | | > 19 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 44); layer = ; configuration = >> 20 | | | | | | | | | | | | ; layer = > 21 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 44); gestureRecognizers = ; layer = > 22 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = ; layer = > 23 | | | | | | | | | | | | | <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView; frame = (20 12; 134.333 20.3333); anchorPoint = (0, 0); opaque = NO; autoresizesSubviews = NO; layer = <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer>> 24 | | | | | | | | | | > 25 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 44); layer = ; configuration = >> 26 | | | | | | | | | | | | ; layer = > 27 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 44); gestureRecognizers = ; layer = > 28 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = ; layer = > 29 | | | | | | | | | | | | | <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView; frame = (20 12; 33.6667 20.3333); anchorPoint = (0, 0); opaque = NO; autoresizesSubviews = NO; layer = <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer>> 30 | | | | | | | | | | > 31 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 44); layer = ; configuration = >> 32 | | | | | | | | | | | | ; layer = > 33 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 44); gestureRecognizers = ; layer = > 34 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = ; layer = > 35 | | | | | | | | | | | | | <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView; frame = (20 12; 46.3333 20.3333); anchorPoint = (0, 0); opaque = NO; autoresizesSubviews = NO; layer = <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer>> 36 | | | | | | | | | | > 37 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 44); layer = ; configuration = >> 38 | | | | | | | | | | | | ; layer = > 39 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 44); gestureRecognizers = ; layer = > 40 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = ; layer = > 41 | | | | | | | | | | | | | <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView; frame = (20 12; 48.3333 20.3333); anchorPoint = (0, 0); opaque = NO; autoresizesSubviews = NO; layer = <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer>> 42 | | | | | | | | | | > 43 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 44); layer = ; configuration = >> 44 | | | | | | | | | | | | ; layer = > 45 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 44); gestureRecognizers = ; layer = > 46 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = ; layer = > 47 | | | | | | | | | | | | | <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView; frame = (20 12; 108.667 20.3333); anchorPoint = (0, 0); opaque = NO; autoresizesSubviews = NO; layer = <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer>> 48 | | | | | | | | | | | | | <_TtC7SwiftUIP33_A34643117F00277B93DEBAB70EC0697122_UIShapeHitTestingView; frame = (325.333 16; 7 12); anchorPoint = (0, 0); autoresizesSubviews = NO; layer = <_TtC7SwiftUIP33_F176A6CF4451B27508D54E2BEAEBFD5415ColorShapeLayer>> 49 | | | | | | | | | | > 50 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 44); layer = ; configuration = >> 51 | | | | | | | | | | | | ; layer = > 52 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 44); gestureRecognizers = ; layer = > 53 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = ; layer = > 54 | | | | | | | | | | | | | <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView; frame = (20 12; 146.333 20.3333); anchorPoint = (0, 0); opaque = NO; autoresizesSubviews = NO; layer = <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer>> 55 | | | | | | | | | | | | | <_TtGC7SwiftUI16PlatformViewHostGVS_P10$1c55533fc32PlatformViewRepresentableAdaptorGVS_P10$1c54e651c18UIKitButtonAdaptorVS_P10$1c54e668819PlatformItemContent___; baseClass = _UIConstraintBasedLayoutHostingView; frame = (263.333 4.66667; 69.6667 34.6667); anchorPoint = (0, 0); tintColor = UIExtendedSRGBColorSpace 1 0.231373 0.188235 1; layer = > 56 | | | | | | | | | | | | | | <_TtC7SwiftUIP33_64A26C7A8406856A733B1A7B593971F725UIKitIconPreferringButton; baseClass = UIButton; frame = (0 0; 69.6667 34.6667); opaque = NO; autoresize = W+H; gestureRecognizers = ; layer = > configuration= baseStyle=plain macStyle=automatic buttonSize=medium cornerStyle=dynamic title=<>:'Night' contentInsets={7, 12, 7, 0} imagePlacement=leading imagePadding=0 titlePadding=1 titleAlignment=automatic automaticallyUpdateForSelection background= 57 | | | | | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 69.6667 34.6667); userInteractionEnabled = NO; layer = ; configuration = > 58 | | | | | | | | | | | | | | | ; layer = > 59 | | | | | | | | | | | | | | | > 60 | | | | | | | | | | > 61 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 44); layer = ; configuration = >> 62 | | | | | | | | | | | | ; layer = > 63 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 44); gestureRecognizers = ; layer = > 64 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = ; layer = > 65 | | | | | | | | | | | | | <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView; frame = (20 12; 149.333 20.3333); anchorPoint = (0, 0); opaque = NO; autoresizesSubviews = NO; layer = <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer>> 66 | | | | | | | | | | > 67 | | | | | | | | | | | <_UISystemBackgroundView; frame = (0 0; 353 44); layer = ; configuration = >> 68 | | | | | | | | | | | | ; layer = > 69 | | | | | | | | | | | <_UICollectionViewListCellContentView; frame = (0 0; 353 44); gestureRecognizers = ; layer = > 70 | | | | | | | | | | | | <_TtGC7SwiftUI15CellHostingViewGVS_15ModifiedContentVS_14_ViewList_ViewVS_26CollectionViewCellModifier__; frame = (0 0; 353 44); autoresize = W+H; gestureRecognizers = ; layer = > 71 | | | | | | | | | | | | | <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8Platform13CGDrawingView; frame = (20 12; 196.667 20.3333); anchorPoint = (0, 0); opaque = NO; autoresizesSubviews = NO; layer = <_TtCOCV7SwiftUI11DisplayList11ViewUpdater8PlatformP33_65A81BD07F0108B0485D2E15DE104A7514CGDrawingLayer>> 72 | | | | | | | | | | <_UICollectionViewListSeparatorView; frame = (40 334.667; 333 0.333333); backgroundColor = ; layer = > 73 | | | | | | | | | | <_UICollectionViewListSeparatorView; frame = (40 378.667; 333 0.333333); backgroundColor = ; layer = > 74 | | | | | | | | | | <_UICollectionViewListSeparatorView; frame = (40 422.667; 333 0.333333); backgroundColor = ; layer = > 75 | | | | | | | | | | <_UICollectionViewListSeparatorView; frame = (40 466.667; 333 0.333333); backgroundColor = ; layer = > 76 | | | | | | | | | | <_UICollectionViewListSeparatorView; frame = (40 510.667; 333 0.333333); backgroundColor = ; layer = > 77 | | | | | | | | | | <_UICollectionViewListSeparatorView; frame = (40 634; 333 0.333333); backgroundColor = ; layer = > 78 | | | | | | | | | | <_UICollectionViewListSeparatorView; frame = (40 678; 333 0.333333); backgroundColor = ; layer = > 79 | | | | | | | | | | <_UIScrollViewScrollIndicator; frame = (387 746; 3 7); alpha = 0; autoresize = LM; layer = > 80 | | | | | | | | | | | > 81 | | | | | > delegate= 82 | | | | | | <_UIBarBackground; frame = (0 0; 393 96); userInteractionEnabled = NO; layer = > 83 | | | | | | | > effect=none 84 | | | | | | | | <_UIVisualEffectBackdropView; frame = (0 0; 393 96); autoresize = W+H; userInteractionEnabled = NO; layer = > 85 | | | | | | | <_UIBarBackgroundShadowView; frame = (0 96; 393 0.333333); layer = > clientRequestedContentView effect=none 86 | | | | | | | | <_UIBarBackgroundShadowContentImageView; frame = (0 0; 393 0.333333); alpha = 0; autoresize = W+H; userInteractionEnabled = NO; backgroundColor = ; image = <(null) (null) anonymous; (0 0)@0>; layer = > 87 | | | | | | <_UINavigationBarLargeTitleView; frame = (0 44; 393 52); clipsToBounds = YES; layer = > 88 | | | | | | | > 89 | | | | | | <_UINavigationBarContentView; frame = (0 0; 393 44); layer = > layout= 90 | | | | | | | <_UIButtonBarStackView; frame = (377 0; 8 44); layer = > axis=horiz distribution=fill alignment=center layoutMarginsRelative NO ARRANGED SUBVIEWS buttonBar= 91 | | | | | | | <_UINavigationBarTitleControl; frame = (148 11.6667; 97.3333 20.3333); layer = > 92 | | | | | | | | > 93 | | | | | | | | | > 94 | | | | | | <_UIPointerInteractionAssistantEffectContainerView; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = > 95 | | | | |