Introduction

Swift property wrappers can expose a projected value (accessed with $property) by defining a projectedValue property. When a property wrapper does not define this, the $ prefix syntax produces a compiler error. This is common when creating custom property wrappers or when expecting a projected value from a third-party wrapper that does not provide one. The projected value is essential for patterns like @State providing Binding, @Published providing Publisher, and @AppStorage providing Binding.

Symptoms

  • Cannot find '$propertyName' in scope compiler error
  • Custom property wrapper does not expose $ prefix access
  • Expected Binding from property wrapper but gets wrapped value instead
  • projectedValue property missing from wrapper type definition
  • Cannot pass $property to child views expecting Binding

Error: ```swift @CustomStorage(key: "username") var username: String = ""

// Error: Value of type 'CustomStorage<String>' has no member 'projectedValue' TextField("Name", text: $username) ```

Common Causes

  • Custom property wrapper missing projectedValue definition
  • Using $ prefix on a wrapper that does not support it
  • Expecting projected value type to match SwiftUI's Binding pattern
  • Wrapper defines wrappedValue but forgets projectedValue
  • Third-party wrapper version does not include projected value

Step-by-Step Fix

  1. 1.Add projectedValue to custom property wrapper:
  2. 2.```swift
  3. 3.@propertyWrapper
  4. 4.struct CustomStorage<T> {
  5. 5.private let key: String
  6. 6.private let defaultValue: T

var wrappedValue: T { get { UserDefaults.standard.object(forKey: key) as? T ?? defaultValue } set { UserDefaults.standard.set(newValue, forKey: key) } }

// WRONG - no projectedValue, so $username does not work

// CORRECT - add projectedValue var projectedValue: Binding<T> { Binding( get: { wrappedValue }, set: { wrappedValue = $0 } ) }

init(key: String, defaultValue: T) { self.key = key self.defaultValue = defaultValue } }

// Now this works: struct SettingsView: View { @CustomStorage(key: "username", defaultValue: "") var username

var body: some View { TextField("Username", text: $username) // $username is Binding<String> } } ```

  1. 1.Project a different type than the wrapped value:
  2. 2.```swift
  3. 3.@propertyWrapper
  4. 4.struct ValidatedText {
  5. 5.private var value: String
  6. 6.private let validator: (String) -> Bool

var wrappedValue: String { get { value } set { value = newValue } }

// Project a validation result instead of a Binding var projectedValue: Bool { validator(value) }

init(wrappedValue: String, validator: @escaping (String) -> Bool) { self.value = wrappedValue self.validator = validator } }

struct SignUpView: View { @ValidatedText(validator: { $0.contains("@") }) var email = ""

var body: some View { VStack { TextField("Email", text: $email) // $email is Binding<String> (default projected value from @State)

// But $email from @ValidatedText is Bool (our custom projected value) if !$email { // Uses projectedValue: Bool Text("Invalid email format").foregroundColor(.red) } } } } ```

  1. 1.Handle property wrapper composition correctly:
  2. 2.```swift
  3. 3.// When combining wrappers, each needs its own projectedValue
  4. 4.@propertyWrapper
  5. 5.struct LoggedValue<T> {
  6. 6.var wrappedValue: T {
  7. 7.didSet {
  8. 8.print("Value changed to \(wrappedValue)")
  9. 9.}
  10. 10.}

// Provide projectedValue as Binding if T is Equatable var projectedValue: Binding<T> { Binding(get: { wrappedValue }, set: { wrappedValue = $0 }) }

init(wrappedValue: T) { self.wrappedValue = wrappedValue } } ```

Prevention

  • Always define projectedValue when creating custom property wrappers
  • Project Binding<T> for UI-related wrappers used with SwiftUI
  • Project Publisher for reactive wrappers
  • Document what $property returns for each custom wrapper
  • Test projected value access in unit tests
  • Use Binding projection for any wrapper that will drive SwiftUI views