Site Logo

EMAN HAROUT

A wrapped gift smiling.

Swift Property Wrappers Gist

Swift property wrappers provide a way to encapsulate logic for getting an setting properties. Here’s a quick tour of their utility.

Creating a Property Wrapper

Here is an example of how to create and use a property wrapper:

// Create
@propertyWrapper
struct UpperCase {
    private var storedValue: String
    var wrappedValue: String {
        set { storedValue = newValue }
        get { storedValue.uppercased() }
    }
    init(wrappedValue: String) {
        self.storedValue = wrappedValue
    }
}

// Use
struct Address {
    @UpperCase public var state: String
}

let address = Address(state: "California")
address.state // CALIFORNIA
  1. Annotate the struct with @propertyWrapper
  2. Define a wrappedValue variable that applies formatting and/or logic.
  3. Add an initializer
  4. Annotate the property with @UpperCase to apply the property wrapper.

Optional: can also use didSet instead of get and set, but this requires repeating formatting/logic in the initializer, since didSet does not execute on the initial assignment.

Initializers and Default Values

There are a few ways to initialize types using property wrappers. For demonstration purposes, I have adjusted the property wrapper like so:

  1. Added a new prefix property to limit the number of characters to uppercase
  2. Declare three initializers
@propertyWrapper
struct UpperCase {
    private var storedValue: String
    private var prefixNumber: Int

    var wrappedValue: String {
        set { storedValue = newValue }
        get {
            let caps = storedValue.uppercased()
            let prefixed = caps.prefix(prefixNumber)
            return String(prefixed)
        }
    }

    // MARK: - Initializers
    init() {
        self.prefixNumber = 3
        self.storedValue = ""
    }

    init(wrappedValue: String) {
        self.prefixNumber = 3
        self.storedValue = wrappedValue
        print(storedValue)
    }

    init(wrappedValue: String, prefix: Int) {
        self.prefixNumber = prefix
        self.storedValue = wrappedValue
    }
}

Using init()

struct Address {
    @UpperCase public var state: String
}
let address = Address() // Calls init()
address.state // ""

Using init(wrappedValue:)

let addressTwo = Address(state: UpperCase(wrappedValue: "Cali")) // Calls init(wrappedValue:)
addressTwo.state // CAL

Note we must pass in UpperCase instance to set state’s value.

Alternatively, we can directly inject a string by modifying Address:

struct AddressV2 {
    @UpperCase public var state: String = "New York" // Calls init(wrappedValue:)
}

let addressV2_default = AddressV2() // Calls init(wrappedValue:)
addressV2_default.state // NEW
let addressV2_california = AddressV2(state: "California") // Calls init(wrappedValue:)
addressV2_california.state // CAL

These examples use init(wrappedValue:).

Using init(wrappedValue: prefix:)

struct AddressV3 {
    @UpperCase(wrappedValue: "CityDefault", prefix: 5)
    public var city: String // Calls init(wrappedValue: prefix:)

    @UpperCase(prefix: 5)
    public var state: String = "StateDefault" // Calls init(wrappedValue: prefix:)
}
let cityStateAddress = AddressV3() // Calls init(wrappedValue: prefix:)
cityStateAddress.city // CITYD
cityStateAddress.state // STATE

Both properties use init(wrappedValue: prefix:) to initialize the property wrapper.

ProjectedValue

A projected value provides added utility to property wrappers.

In this example, we create a projectedValue that returns true if the original string exceeds the defined prefix length:

@propertyWrapper
struct UpperCase {
    private var storedValue: String
    private var prefixNumber: Int
    var projectedValue: Bool { storedValue.count > prefixNumber } 

    var wrappedValue: String {
        set { storedValue = newValue }
        get {
            let caps = storedValue.uppercased()
            let prefixed = caps.prefix(prefixNumber)
            return String(prefixed)
        }
    }

    init(wrappedValue: String, prefix: Int) {
        self.prefixNumber = prefix
        self.storedValue = wrappedValue
    }
}

To reference projectedValue, we use the $ symbol before the property name:

struct Address {
    @UpperCase public var state: String
}

var address = Address(state: UpperCase(wrappedValue: "Cali", prefix: 2))
address.$state // true
address.state = "C"
address.$state // false

Function Parameters

Swift allows for the use of property wrappers in function parameters.

func toUpperCase(@UpperCase message: String) {
    print(message)
}

toUpperCase(message: "new york")
// prints "NEW"

You can configure the property wrapper with default arguments too:

func toUpperCase(@UpperCase(prefix: 9) message: String) {
    print(message)
}

toUpperCase(message: "california")
// prints "CALIFORNI"