Perception

repository·main·Indexed 21 days ago

https://github.com/pointfreeco/swift-perception

A library that back-ports Swift's modern Observation framework—including @Observable, withObservationTracking, and Observations—to older Apple platforms such as iOS 13, macOS 10.15, tvOS 13, and watchOS 6. It provides the @Perceptible macro, WithPerceptionTracking view wrapper, @Perception.Bindable for two-way bindings, and the Perceptions async sequence for tracking changes over time.

Tokens
1.6K
Snippets
10
Records
12
Agent score
72%

What's inside Perception

  1. Overview of Perception

    main

    Perception provides Swift's Observation tools, back-ported to support a wider range of platforms. The library is split into two main parts:

    1. PerceptionCore: Contains the core functionality of the library.
    2. Perception: Automatically exports PerceptionCore and provides the macros required to support the core functionality.

    For general library usage and core logic, refer to the PerceptionCore documentation.

  2. Use Perception macros for tracking and ignoring properties

    main

    Perception provides macros to fine-tune how properties are observed:

    • @PerceptionTracked: Marks a property to be tracked by the observation system. This is the default behavior for properties in a @Perceptible type.
    • @PerceptionIgnored: Marks a property so that changes to it do not trigger observation updates. Use this for properties that should not affect the UI or other observers.
  3. How to use Perception for observation

    main

    Perception back-ports Swift's Observation tools (@Observable, withObservationTracking, and Observations) to older platforms like iOS 13, macOS 10.15, tvOS 13, and watchOS 6.

    To use it, follow these three steps:

    1. Mark your class with the @Perceptible macro.
    2. Hold the model in your view using a standard let property.
    3. Wrap your view's content in a WithPerceptionTracking view to enable observation tracking.

    If you access a perceptible property without using WithPerceptionTracking, you will receive a runtime warning: 🟣 Runtime Warning: Perceptible state '\FeatureModel.count' was accessed from a view but is not being tracked.

    @Perceptible
    class FeatureModel {
      var count = 0
    }
    
    struct FeatureView: View {
      let model: FeatureModel
    
      var body: some View {
        WithPerceptionTracking {
          Form {
            Text("\(model.count)")
            Button("Increment") { model.count += 1 }
          }
        }
      }
    }
  4. Use @Environment with perceptible objects

    main

    Perception supports the standard SwiftUI @Environment property wrapper and .environment() view modifier for perceptible objects. You can inject perceptible objects into the environment and access them using the same APIs used for standard SwiftUI observation.

    struct FeatureView: View {
      @Environment(Settings.self) var settings
    
      // ...
    }
    
    // In some parent view:
    .environment(settings)
  5. Observe changes in SwiftUI with WithPerceptionTracking

    main

    In SwiftUI, you must wrap your view's content in the WithPerceptionTracking view to ensure observation is correctly hooked up. If you access a perceptible property inside a view's body without this wrapper, you will receive a runtime warning:

    🟣 Runtime Warning: Perceptible state '\FeatureModel.count' was accessed from a view but is not being tracked.

    struct FeatureView: View {
      let model: FeatureModel
    
      var body: some View {
        WithPerceptionTracking {
          Form {
            Text("\(model.count)")
            Button("Increment") { model.count += 1 }
          }
        }
      }
    }
  6. Get started with Perception

    main

    Perception back-ports Swift's Observation tools (@Observable, withObservationTracking, and Observations) to older platforms like iOS 13, macOS 10.15, tvOS 13, and watchOS 6.

    To use it, import Perception (which automatically imports PerceptionCore). The workflow is nearly identical to official Swift Observation, with the primary difference being the use of the @Perceptible macro and the WithPerceptionTracking view wrapper.

    @Perceptible
    class FeatureModel {
      var count = 0
    }
  7. Observe changes over time with Perceptions

    main

    The Observations async sequence is back-ported as Perceptions. You can use it to create an async sequence that emits new values whenever a specific property on a perceptible or observable object changes.

    let counts = Perceptions { model.count }
    for await count in counts {
      print("Count changed: \(count)")
    }
  8. Use @Perception.Bindable for perceptible objects

    main

    To support two-way binding for perceptible objects in SwiftUI, use the @Perception.Bindable property wrapper. This is the back-ported equivalent of SwiftUI's @Bindable.

    struct FeatureView: View {
      @Perception.Bindable var model: FeatureModel
    
      // ...
    }