Skip to content

Commit

Permalink
Add support for SwiftUI environment (#13)
Browse files Browse the repository at this point in the history
* Add support for SwiftUI environment

* Update ExampleApp.swift

* Update ExampleApp.swift

* Update Environment.swift

* Update Environment.swift

* Update Environment.swift

* wip

* README
  • Loading branch information
stephencelis authored Jan 12, 2024
1 parent 6409a03 commit ee1d361
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 0 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,35 @@ set up correctly:
> 🟣 Runtime Warning: Perceptible state was accessed but is not being tracked. Track changes to
> state by wrapping your view in a 'WithPerceptionTracking' view.
### Bindable

SwiftUI's `@Bindable` property wrapper has also been backported to support perceptible objects. You
can simply qualify the property wrapper with the `Perception` module:

```swift
struct FeatureView: View {
@Perception.Bindable var model: FeatureModel

// ...
}
```

### Environment

SwiftUI's `@Environment` property wrapper and `environment` view modifier's support for observation
has also been backported to support perceptible objects using the exact same APIs:

```swift
struct FeatureView: View {
@Environment(Settings.self) var settings

// ...
}

// In some parent view:
.environment(settings)
```

## Community

If you want to discuss this library or have a question about how to use it to solve
Expand Down
74 changes: 74 additions & 0 deletions Sources/Perception/Environment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import SwiftUI

@available(iOS, introduced: 13, obsoleted: 17)
@available(macOS, introduced: 10.15, obsoleted: 14)
@available(tvOS, introduced: 13, obsoleted: 17)
@available(watchOS, introduced: 6, obsoleted: 10)
@available(visionOS, unavailable)
extension Environment {
/// Creates an environment property to read a perceptible object from the environment.
///
/// A backport of SwiftUI's `Environment.init` that takes an observable object.
///
/// - Parameter objectType: The type of the `Perceptible` object to read from the environment.
@_disfavoredOverload
public init(_ objectType: Value.Type) where Value: AnyObject & Perceptible {
self.init(\.[unwrap: \Value.self])
}

/// Creates an environment property to read a perceptible object from the environment, returning
/// `nil` if no corresponding object has been set in the current view's environment.
///
/// A backport of SwiftUI's `Environment.init` that takes an observable object.
///
/// - Parameter objectType: The type of the `Perceptible` object to read from the environment.
@_disfavoredOverload
public init<T: AnyObject & Perceptible>(_ objectType: T.Type) where Value == T? {
self.init(\.[\T.self])
}
}

@available(iOS, introduced: 13, obsoleted: 17)
@available(macOS, introduced: 10.15, obsoleted: 14)
@available(tvOS, introduced: 13, obsoleted: 17)
@available(watchOS, introduced: 6, obsoleted: 10)
@available(visionOS, unavailable)
extension View {
/// Places a perceptible object in the view’s environment.
///
/// A backport of SwiftUI's `View.environment` that takes an observable object.
///
/// - Parameter object: The object to set for this object's type in the environment, or `nil` to
/// clear an object of this type from the environment.
/// - Returns: A view that has the specified object in its environment.
@_disfavoredOverload
public func environment<T: AnyObject & Perceptible>(_ object: T?) -> some View {
self.environment(\.[\T.self], object)
}
}

private struct PerceptibleKey<T: Perceptible>: EnvironmentKey {
static var defaultValue: T? { nil }
}

extension EnvironmentValues {
fileprivate subscript<T: Perceptible>(_: KeyPath<T, T>) -> T? {
get { self[PerceptibleKey<T>.self] }
set { self[PerceptibleKey<T>.self] = newValue }
}

fileprivate subscript<T: Perceptible>(unwrap _: KeyPath<T, T>) -> T {
get {
guard let object = self[\T.self] else {
fatalError(
"""
No perceptible object of type \(T.self) found. A View.environment(_:) for \(T.self) may \
be missing as an ancestor of this view.
"""
)
}
return object
}
set { self[\T.self] = newValue }
}
}

0 comments on commit ee1d361

Please sign in to comment.