Environment.swift 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /// A property wrapper used to access environment values within a ``View`` or
  2. /// ``App``.
  3. ///
  4. /// Must not be used before the view graph accesses the view or app's `body`
  5. /// (so, don't access it from an initializer).
  6. ///
  7. /// ```swift
  8. /// struct ContentView: View {
  9. /// @Environment(\.colorScheme) var colorScheme
  10. ///
  11. /// var body: some View {
  12. /// Text("Current color scheme: \(colorScheme)")
  13. /// .background(colorScheme == .light ? Color.black : Color.white)
  14. /// }
  15. /// }
  16. /// ```
  17. ///
  18. /// The environment also contains UI-related actions, such as the
  19. /// ``EnvironmentValues/chooseFile`` action used to present 'Open file' dialogs.
  20. ///
  21. /// ```swift
  22. /// struct ContentView: View {
  23. /// @Environment(\.chooseFile) var chooseFile
  24. ///
  25. /// var body: some View {
  26. /// Button("Open") {
  27. /// Task {
  28. /// guard let file = await chooseFile() else {
  29. /// print("No file chosen")
  30. /// return
  31. /// }
  32. ///
  33. /// print("The user chose: \(file.path)")
  34. /// }
  35. /// }
  36. /// }
  37. /// }
  38. /// ```
  39. @propertyWrapper
  40. public struct Environment<Value>: DynamicProperty {
  41. private var mode: Mode
  42. /// The underlying value.
  43. ///
  44. /// `nil` if ``update(with:previousValue:)`` has not yet been called.
  45. private var value: Box<Value?>
  46. public func update(
  47. with environment: EnvironmentValues,
  48. previousValue: Self?
  49. ) {
  50. switch mode {
  51. case .keyPath(let keyPath):
  52. value.value = environment[keyPath: keyPath]
  53. case .observableObject:
  54. if let type = Value.self as? any ObservableObject.Type {
  55. value.value = (environment[observable: type] as! Value)
  56. }
  57. }
  58. }
  59. /// The environment value that this property refers to.
  60. public var wrappedValue: Value {
  61. guard let value = value.value else {
  62. fatalError(
  63. """
  64. Environment value at \(mode.pathDescription) used before initialization. Don't \
  65. use @Environment properties before SwiftCrossUI requests the \
  66. view's body.
  67. """
  68. )
  69. }
  70. return value
  71. }
  72. /// Initializes an ``Environment`` property wrapper.
  73. ///
  74. /// - Parameter keyPath: A key path to the enviornment value to access.
  75. public init(_ keyPath: KeyPath<EnvironmentValues, Value>) {
  76. self.value = Box(nil)
  77. self.mode = .keyPath(keyPath)
  78. }
  79. public init(_ type: Value.Type) where Value: ObservableObject {
  80. self.value = Box(nil)
  81. self.mode = .observableObject
  82. }
  83. private enum Mode {
  84. /// A key path to the enviornment value to access.
  85. case keyPath(KeyPath<EnvironmentValues, Value>)
  86. /// An observable object.
  87. case observableObject
  88. var pathDescription: String {
  89. switch self {
  90. case .keyPath(let keyPath):
  91. "\(keyPath)"
  92. case .observableObject:
  93. "\(Value.self).self"
  94. }
  95. }
  96. }
  97. }