How does propertyWrapper composition work vis-à-vis SwiftUI @Environment(\.presentationMode) @Binding?

// this is a Binding<T>
@Environment(\.presentationMode) var presentationMode1          // Binding<PresentationMode>
// how does this become just `T`   <===     ?
@Environment(\.presentationMode) @Binding var presentationMode2 // PresentationMode

?

My Test
import SwiftUI

struct ContentView: View {
    @Environment(\.presentationMode) var presentationMode1          // Binding<PresentationMode>
    @Environment(\.presentationMode) @Binding var presentationMode2 // PresentationMode

    var body: some View {
        VStack {
            Text("Hello, world!")
                .padding()
            Button("Show Me") {
                print("hello")
                print(type(of: presentationMode1))      // prints: Binding<PresentationMode>
                print(type(of: presentationMode2))      // prints: PresentationMode
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

When you compose property wrappers, the next property wrapper in the chain becomes the wrapped value of the previous property wrapper. The wrapped property itself represents the wrapped value of the innermost property wrapper. Type inference will iterate over the chain of property wrapper attributes, equating the wrapped-value type of the previous property wrapper with the type of the next property wrapper.

So, for this example:

@Environment(\.presentationMode) @Binding var presentationMode2

Type inference starts with the outermost property wrapper, which is @Environment. It will then equate the wrapped-value type of Environment with the type of the next property wrapper, which is Binding. The wrapped value type of Environment is determined by the key-path value type (in this case, the type of EnvironmentValues.presentationMode), which is Binding<PresentationMode>. When the compiler equates this wrapped-value type with the next property wrapper, the generic argument for @Binding is inferred to be PresentationMode. Then, the type of the property can be inferred as the wrapped-value type of Binding<PresentationMode>, which is PresentationMode.

6 Likes