|
| 1 | +// |
| 2 | +// ColorScheme.swift |
| 3 | +// OpenSwiftUI |
| 4 | +// |
| 5 | +// Audited for iOS 18.0 |
| 6 | +// Status: Complete |
| 7 | +// ID: 387C753F3FFD2899BCB77252214CFCC6 (SwiftUI) |
| 8 | +// ID: 0E72AB1FBE33AED1E73FF06F3DA3A071 (SwiftUICore) |
| 9 | + |
| 10 | +package import OpenGraphShims |
| 11 | + |
| 12 | +// MARK: - ColorScheme |
| 13 | + |
| 14 | +/// The possible color schemes, corresponding to the light and dark appearances. |
| 15 | +/// |
| 16 | +/// You receive a color scheme value when you read the |
| 17 | +/// ``EnvironmentValues/colorScheme`` environment value. The value tells you if |
| 18 | +/// a light or dark appearance currently applies to the view. OpenSwiftUI updates |
| 19 | +/// the value whenever the appearance changes, and redraws views that |
| 20 | +/// depend on the value. For example, the following ``Text`` view automatically |
| 21 | +/// updates when the user enables Dark Mode: |
| 22 | +/// |
| 23 | +/// @Environment(\.colorScheme) private var colorScheme |
| 24 | +/// |
| 25 | +/// var body: some View { |
| 26 | +/// Text(colorScheme == .dark ? "Dark" : "Light") |
| 27 | +/// } |
| 28 | +/// |
| 29 | +/// Set a preferred appearance for a particular view hierarchy to override |
| 30 | +/// the user's Dark Mode setting using the ``View/preferredColorScheme(_:)`` |
| 31 | +/// view modifier. |
| 32 | +public enum ColorScheme: CaseIterable, Sendable { |
| 33 | + /// The color scheme that corresponds to a light appearance. |
| 34 | + case light |
| 35 | + |
| 36 | + /// The color scheme that corresponds to a dark appearance. |
| 37 | + case dark |
| 38 | +} |
| 39 | + |
| 40 | +// MARK: - ColorSchemeContrast |
| 41 | + |
| 42 | +/// The contrast between the app's foreground and background colors. |
| 43 | +/// |
| 44 | +/// You receive a contrast value when you read the |
| 45 | +/// ``EnvironmentValues/colorSchemeContrast`` environment value. The value |
| 46 | +/// tells you if a standard or increased contrast currently applies to the view. |
| 47 | +/// OpenSwiftUI updates the value whenever the contrast changes, and redraws |
| 48 | +/// views that depend on the value. For example, the following ``Text`` view |
| 49 | +/// automatically updates when the user enables increased contrast: |
| 50 | +/// |
| 51 | +/// @Environment(\.colorSchemeContrast) private var colorSchemeContrast |
| 52 | +/// |
| 53 | +/// var body: some View { |
| 54 | +/// Text(colorSchemeContrast == .standard ? "Standard" : "Increased") |
| 55 | +/// } |
| 56 | +/// |
| 57 | +/// The user sets the contrast by selecting the Increase Contrast option in |
| 58 | +/// Accessibility > Display in System Preferences on macOS, or |
| 59 | +/// Accessibility > Display & Text Size in the Settings app on iOS. |
| 60 | +/// Your app can't override the user's choice. For |
| 61 | +/// information about using color and contrast in your app, see |
| 62 | +/// [Accessibility](https://developer.apple.com/design/human-interface-guidelines/accessibility#Color-and-effects). |
| 63 | +/// in the Human Interface Guidelines. |
| 64 | +public enum ColorSchemeContrast: CaseIterable, Sendable { |
| 65 | + |
| 66 | + /// OpenSwiftUI displays views with standard contrast between the app's |
| 67 | + /// foreground and background colors. |
| 68 | + case standard |
| 69 | + |
| 70 | + /// OpenSwiftUI displays views with increased contrast between the app's |
| 71 | + /// foreground and background colors. |
| 72 | + case increased |
| 73 | +} |
| 74 | + |
| 75 | +// MARK: - View + ColorScheme |
| 76 | + |
| 77 | +extension View { |
| 78 | + /// Sets this view's color scheme. |
| 79 | + /// |
| 80 | + /// Use `colorScheme(_:)` to set the color scheme for the view to which you |
| 81 | + /// apply it and any subviews. If you want to set the color scheme for all |
| 82 | + /// views in the presentation, use ``View/preferredColorScheme(_:)`` |
| 83 | + /// instead. |
| 84 | + /// |
| 85 | + /// - Parameter colorScheme: The color scheme for this view. |
| 86 | + /// |
| 87 | + /// - Returns: A view that sets this view's color scheme. |
| 88 | + @inlinable |
| 89 | + nonisolated public func colorScheme(_ colorScheme: ColorScheme) -> some View { |
| 90 | + environment(\.colorScheme, colorScheme) |
| 91 | + } |
| 92 | +} |
| 93 | + |
| 94 | +// MARK: - ColorScheme + EnvironmentValues |
| 95 | + |
| 96 | +extension EnvironmentValues { |
| 97 | + /// The color scheme of this environment. |
| 98 | + /// |
| 99 | + /// Read this environment value from within a view to find out if OpenSwiftUI |
| 100 | + /// is currently displaying the view using the ``ColorScheme/light`` or |
| 101 | + /// ``ColorScheme/dark`` appearance. The value that you receive depends on |
| 102 | + /// whether the user has enabled Dark Mode, possibly superseded by |
| 103 | + /// the configuration of the current presentation's view hierarchy. |
| 104 | + /// |
| 105 | + /// @Environment(\.colorScheme) private var colorScheme |
| 106 | + /// |
| 107 | + /// var body: some View { |
| 108 | + /// Text(colorScheme == .dark ? "Dark" : "Light") |
| 109 | + /// } |
| 110 | + /// |
| 111 | + /// You can set the `colorScheme` environment value directly, |
| 112 | + /// but that usually isn't what you want. Doing so changes the color |
| 113 | + /// scheme of the given view and its child views but *not* the views |
| 114 | + /// above it in the view hierarchy. Instead, set a color scheme using the |
| 115 | + /// ``View/preferredColorScheme(_:)`` modifier, which also propagates the |
| 116 | + /// value up through the view hierarchy to the enclosing presentation, like |
| 117 | + /// a sheet or a window. |
| 118 | + /// |
| 119 | + /// When adjusting your app's user interface to match the color scheme, |
| 120 | + /// consider also checking the ``EnvironmentValues/colorSchemeContrast`` |
| 121 | + /// property, which reflects a system-wide contrast setting that the user |
| 122 | + /// controls. For information, see |
| 123 | + /// [Accessibility](https://developer.apple.com/design/human-interface-guidelines/accessibility#Color-and-effects). |
| 124 | + /// in the Human Interface Guidelines. |
| 125 | + /// |
| 126 | + /// > Note: If you only need to provide different colors or |
| 127 | + /// images for different color scheme and contrast settings, do that in |
| 128 | + /// your app's Asset Catalog. See |
| 129 | + /// [Asset management](https://developer.apple.com/documentation/xcode/asset-management). |
| 130 | + public var colorScheme: ColorScheme { |
| 131 | + get { self[ColorSchemeKey.self] } |
| 132 | + set { self[ColorSchemeKey.self] = newValue } |
| 133 | + } |
| 134 | + |
| 135 | + package var explicitPreferredColorScheme: ColorScheme? { |
| 136 | + get { self[ExplicitPreferredColorSchemeKey.self] } |
| 137 | + set { self[ExplicitPreferredColorSchemeKey.self] = newValue } |
| 138 | + } |
| 139 | + |
| 140 | + package var systemColorScheme: ColorScheme { |
| 141 | + get { self[SystemColorSchemeKey.self] } |
| 142 | + set { self[SystemColorSchemeKey.self] = newValue } |
| 143 | + } |
| 144 | + |
| 145 | + /// The contrast associated with the color scheme of this environment. |
| 146 | + /// |
| 147 | + /// Read this environment value from within a view to find out if OpenSwiftUI |
| 148 | + /// is currently displaying the view using ``ColorSchemeContrast/standard`` |
| 149 | + /// or ``ColorSchemeContrast/increased`` contrast. The value that you read |
| 150 | + /// depends entirely on user settings, and you can't change it. |
| 151 | + /// |
| 152 | + /// @Environment(\.colorSchemeContrast) private var colorSchemeContrast |
| 153 | + /// |
| 154 | + /// var body: some View { |
| 155 | + /// Text(colorSchemeContrast == .standard ? "Standard" : "Increased") |
| 156 | + /// } |
| 157 | + /// |
| 158 | + /// When adjusting your app's user interface to match the contrast, |
| 159 | + /// consider also checking the ``EnvironmentValues/colorScheme`` property |
| 160 | + /// to find out if OpenSwiftUI is displaying the view with a light or dark |
| 161 | + /// appearance. For information, see |
| 162 | + /// [Accessibility](https://developer.apple.com/design/human-interface-guidelines/accessibility#Color-and-effects). |
| 163 | + /// in the Human Interface Guidelines. |
| 164 | + /// |
| 165 | + /// > Note: If you only need to provide different colors or |
| 166 | + /// images for different color scheme and contrast settings, do that in |
| 167 | + /// your app's Asset Catalog. See |
| 168 | + /// [Asset management](https://developer.apple.com/documentation/xcode/asset-management). |
| 169 | + public var colorSchemeContrast: ColorSchemeContrast { |
| 170 | + self[ColorSchemeContrastKey.self] |
| 171 | + } |
| 172 | + |
| 173 | + public var _colorSchemeContrast: ColorSchemeContrast { |
| 174 | + get { self[ColorSchemeContrastKey.self] } |
| 175 | + set { self[ColorSchemeContrastKey.self] = newValue } |
| 176 | + } |
| 177 | +} |
| 178 | + |
| 179 | +private struct ColorSchemeKey: EnvironmentKey { |
| 180 | + static let defaultValue: ColorScheme = .light |
| 181 | +} |
| 182 | + |
| 183 | +private struct SystemColorSchemeKey: EnvironmentKey { |
| 184 | + static let defaultValue: ColorScheme = .light |
| 185 | +} |
| 186 | + |
| 187 | +private struct ExplicitPreferredColorSchemeKey: EnvironmentKey { |
| 188 | + static let defaultValue: ColorScheme? = nil |
| 189 | +} |
| 190 | + |
| 191 | +private struct ColorSchemeContrastKey: EnvironmentKey { |
| 192 | + static var defaultValue: ColorSchemeContrast = .standard |
| 193 | +} |
| 194 | + |
| 195 | +// MARK: - SystemColorSchemeModifier |
| 196 | + |
| 197 | +@MainActor |
| 198 | +@preconcurrency |
| 199 | +package struct SystemColorSchemeModifier: ViewModifier, PrimitiveViewModifier, EnvironmentModifier { |
| 200 | + package var isEnabled: Bool |
| 201 | + |
| 202 | + package init(isEnabled: Bool) { |
| 203 | + self.isEnabled = isEnabled |
| 204 | + } |
| 205 | + |
| 206 | + nonisolated package static func makeEnvironment(modifier: Attribute<SystemColorSchemeModifier>, environment: inout EnvironmentValues) { |
| 207 | + guard modifier.value.isEnabled else { return } |
| 208 | + environment.colorScheme = environment.systemColorScheme |
| 209 | + } |
| 210 | + |
| 211 | + package typealias Body = Never |
| 212 | +} |
| 213 | + |
| 214 | +// MARK: - ColorScheme + ProtobufEnum |
| 215 | + |
| 216 | +extension ColorScheme: ProtobufEnum { |
| 217 | + package var protobufValue: UInt { |
| 218 | + switch self { |
| 219 | + case .light: return 0 |
| 220 | + case .dark: return 1 |
| 221 | + } |
| 222 | + } |
| 223 | + |
| 224 | + package init?(protobufValue: UInt) { |
| 225 | + switch protobufValue { |
| 226 | + case 0: self = .light |
| 227 | + case 1: self = .dark |
| 228 | + default: return nil |
| 229 | + } |
| 230 | + } |
| 231 | +} |
| 232 | + |
| 233 | +// MARK: - ColorSchemeContrast + ProtobufEnum |
| 234 | + |
| 235 | +extension ColorSchemeContrast: ProtobufEnum { |
| 236 | + package var protobufValue: UInt { |
| 237 | + switch self { |
| 238 | + case .standard: return 0 |
| 239 | + case .increased: return 1 |
| 240 | + } |
| 241 | + } |
| 242 | + |
| 243 | + package init?(protobufValue: UInt) { |
| 244 | + switch protobufValue { |
| 245 | + case 0: self = .standard |
| 246 | + case 1: self = .increased |
| 247 | + default: return nil |
| 248 | + } |
| 249 | + } |
| 250 | +} |
0 commit comments