SWIFT

Swift Property Wrappers: Syntax, Customization, and Use Cases

Property wrappers in Swift, introduced in version 5.1, let you define reusable behavior for properties. They’re ideal for handling value transformation, validation, caching, or logging—without duplicating logic across multiple places. Wrappers make your code more modular, expressive, and easier to maintain, especially when working with recurring patterns.


What Is a Property Wrapper?

A property wrapper is a type—usually a struct—that encapsulates the logic for reading and writing a property’s value. Instead of placing code inside get and set, you delegate that behavior to a reusable wrapper.

Basic Syntax

To create one, define a type with the @propertyWrapper attribute and include a wrappedValue property:

@propertyWrapper
struct Clamped {
    private var value: Int
    private let range: ClosedRange<Int>

    var wrappedValue: Int {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    init(wrappedValue: Int, _ range: ClosedRange<Int>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

You can then apply it to a property:

@Clamped(0...100) var volume: Int = 50

volume = 120
print(volume) // 100

The wrapper enforces the value range automatically.


Why Use Them?

Wrappers help eliminate repetitive logic. Common use cases include:

  • Input validation (e.g., clamping, trimming)
  • Logging or tracking value changes
  • Integrating with UserDefaults
  • Lazy or thread-safe initialization
  • Data formatting or transformation

Once defined, a wrapper can be reused with a simple @WrapperName declaration.


Key Components

A minimal property wrapper includes:

  • The @propertyWrapper annotation
  • A wrappedValue computed property
  • An initializer that receives the original value

You can optionally expose a projectedValue with $ syntax or access self in init.


Example: Trim Whitespace

This wrapper trims whitespace from assigned strings:

@propertyWrapper
struct Trimmed {
    private var value: String = ""

    var wrappedValue: String {
        get { value }
        set { value = newValue.trimmingCharacters(in: .whitespacesAndNewlines) }
    }

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue
    }
}

Usage:

@Trimmed var name: String = "  Alice  "
print(name) // "Alice"

Cleaner and more consistent than trimming manually every time.


Establishing Default Behavior

Default values passed to a property are forwarded to the wrapper’s initializer:

@propertyWrapper
struct Capitalized {
    private var value: String = ""

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

Applied like this:

@Capitalized var title: String = "developer"
print(title) // "Developer"

This helps enforce formatting rules throughout your codebase.


Projected Values with $ Syntax

Wrappers can expose additional data through projectedValue, accessed with a dollar sign:

@propertyWrapper
struct Logged<T> {
    private var value: T
    var changes: [T] = []

    var wrappedValue: T {
        get { value }
        set {
            changes.append(newValue)
            value = newValue
        }
    }

    var projectedValue: [T] {
        return changes
    }

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

Usage:

@Logged var score: Int = 0

score = 5
score = 10

print($score) // [5, 10]

This gives you insight into how values have changed over time.


Built-In Use in SwiftUI

SwiftUI heavily relies on wrappers for state management:

  • @State: local view state
  • @Binding: two-way binding between views
  • @Environment: reads values from the view hierarchy
  • @ObservedObject and @StateObject: observable models

These wrappers are fundamental to SwiftUI’s declarative data flow. When you declare @State var isOn = false, you're tapping into this powerful mechanism.


Combining Multiple Wrappers

You can nest multiple wrappers on the same property. Swift applies them from the inside out:

@propertyWrapper
struct Uppercase {
    private var value: String = ""
    var wrappedValue: String {
        get { value }
        set { value = newValue.uppercased() }
    }
}

@Uppercase
@Trimmed
var greeting: String = "  hello  "

print(greeting) // "HELLO"

First, the string is trimmed, then uppercased.


Wrapping Complex Types

Wrappers aren’t limited to primitive types. You can use them with collections, optionals, or custom structs:

@propertyWrapper
struct NonEmptyArray<T> {
    private var array: [T]

    var wrappedValue: [T] {
        get { array }
        set { array = newValue.isEmpty ? [T]() : newValue }
    }

    init(wrappedValue: [T]) {
        self.array = wrappedValue
    }
}

Usage:

@NonEmptyArray var items: [Int] = []

This ensures items will never remain empty unintentionally.


Limitations to Keep in Mind

While wrappers are powerful, they come with a few restrictions:

  • They can’t be applied to computed properties
  • Not supported on global variables
  • May obscure logic if overused
  • Debugging can be slightly harder due to indirection
  • You must manually handle Codable compliance for wrapped properties

Use them for clean encapsulation—not to hide complexity or introduce side effects.


Codable Support

To make your wrapper compatible with Codable, you’ll need to add conformance manually:

@propertyWrapper
struct Uppercase: Codable {
    var wrappedValue: String

    init(wrappedValue: String) {
        self.wrappedValue = wrappedValue.uppercased()
    }
}

Be sure to implement custom encoding/decoding logic in the containing type if necessary.


Summary

Property wrappers let you define and reuse custom logic for property behavior in a clean, declarative way. With tools like wrappedValue, projectedValue, and flexible initializers, they enable you to handle transformation, validation, and tracking consistently across your codebase.

They're essential in SwiftUI and valuable anywhere modular, reusable design matters. Whether you're building lightweight utilities or powerful abstractions, mastering property wrappers gives you another layer of control and clarity in modern Swift development.

Learn to Code in Swift for Free
Start learning now
button icon
To advance beyond this tutorial and learn Swift by doing, try the interactive experience of Mimo. Whether you're starting from scratch or brushing up your coding skills, Mimo helps you take your coding journey above and beyond.

Sign up or download Mimo from the App Store or Google Play to enhance your programming skills and prepare for a career in tech.

You can code, too.

© 2025 Mimo GmbH

Reach your coding goals faster