swiftswiftui

Property wrappers and SwiftUI environment: how can a property wrapper access the environment of its enclosing object?


The @FetchRequest property wrapper that ships with SwiftUI helps declaring properties that are auto-updated whenever a Core Data storage changes. You only have to provide a fetch request:

struct MyView: View {
    @FetchRequest(fetchRequest: /* some fetch request */)
    var myValues: FetchedResults<MyValue>
}

The fetch request can't access the storage without a managed object context. This context has to be passed in the view's environment.

And now I'm quite puzzled.

Is there any public API that allows a property wrapper to access the environment of its enclosing object, or to have SwiftUI give this environment to the property wrapper?


Solution

  • With Xcode 13 (haven't tested on earlier versions) as long as your property wrapper implements DynamicProperty you can use the @Environment property wrapper.

    The following example create a property wrapper that's read the lineSpacing from the current environment.

    @propertyWrapper
    struct LineSpacing: DynamicProperty {
        @Environment(\.lineSpacing) var lineSpacing: CGFloat
        
        var wrappedValue: CGFloat {
            lineSpacing
        }
    }
    

    Then you can use it just like any other property wrapper:

    struct LineSpacingDisplayView: View {
        @LineSpacing private var lineSpacing: CGFloat
        
        var body: some View {
            Text("Line spacing: \(lineSpacing)")
        }
    }
    
    struct ContentView: View {
        var body: some View {
            VStack {
                LineSpacingDisplayView()
                LineSpacingDisplayView()
                    .environment(\.lineSpacing, 99)
            }
        }
    }
    

    This displays:

    Line spacing: 0.000000

    Line spacing: 99.000000