SWIFT

Swift Property Wrapper: Syntax, Customization, and Use Cases

A Swift property wrapper is a powerful feature introduced in Swift 5.1 that lets developers define reusable behavior for properties. With this mechanism, you can add functionality like value transformation, logging, caching, or validation to properties without duplicating code. A property wrapper Swift makes your code more expressive, modular, and maintainable—especially when working with repetitive logic across multiple properties.


Understanding the Basics of Swift Property Wrappers

A property wrapper is essentially a struct, class, or enum that encapsulates logic for reading and writing a value. Instead of writing code directly inside a property’s get and set, you apply a wrapper that handles it for you.

Basic Syntax

To declare a wrapper, define a type marked with the @propertyWrapper attribute and implement 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)
    }
}

Then apply it to any property like this:

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

Assigning a value outside the specified range automatically clamps it:

volume = 120
print(volume) // Outputs: 100

Why Use Property Wrappers in Swift

The purpose of a Swift property wrapper is to eliminate repetitive logic. Common reasons to use wrappers include:

  • Input validation (e.g., clamping numbers, trimming strings)
  • Synchronizing with user defaults
  • Logging changes
  • Lazy initialization
  • Injecting behavior like thread safety or caching

You can encapsulate all this behavior and apply it across properties just by writing @WrapperName.


Components of a Property Wrapper Swift Supports

At minimum, you need:

  • @propertyWrapper annotation
  • A wrappedValue computed property
  • An initializer

You can also expose additional behaviors with projectedValue or access to self within init.


Example: Trimming Whitespace with a Custom Wrapper

Here's a simple Swift custom property wrapper that 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) // Outputs: "Alice"

This is cleaner than writing .trimmingCharacters every time you assign a value.


Using Default Values in Property Wrappers

The initial value passed during property declaration is forwarded to the wrapper's init(wrappedValue:). You can leverage this to establish default behavior:

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

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

Then:

@Capitalized var title: String = "manager"
print(title) // Outputs: "Manager"

This avoids repeating .capitalized logic throughout the codebase.


Accessing the Projected Value

Property wrappers can also expose an additional value using the $ prefix via projectedValue.

@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
    }
}

Use it like this:

@Logged var score: Int = 0

score = 5
score = 10

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

$score gives you access to the history of changes, separate from the current value.


Property Wrappers in SwiftUI

SwiftUI makes extensive use of property wrappers like:

  • @State: for local view state
  • @Binding: to create two-way bindings
  • @Environment: to read values from the view hierarchy
  • @ObservedObject and @StateObject: for observing model changes

These wrappers are foundational for the SwiftUI data flow model. When you declare @State var isActive = false, you’re using a system-defined Swift property wrapper behind the scenes to track and update the view state.


Nesting Property Wrappers

You can use multiple property wrappers on the same property, but it requires careful handling. 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) // Outputs: "HELLO"

In this example, Trimmed is applied first, then Uppercase.


Wrapping Non-Primitive Types

A property wrapper Swift pattern isn't limited to strings or integers. You can wrap custom structs, arrays, dictionaries, or even optionals.

@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
    }
}

Use it to ensure arrays never stay empty:

@NonEmptyArray var items: [Int] = []

Limitations of Property Wrappers

While powerful, Swift property wrappers come with some constraints:

  • Wrappers can't be applied to computed properties
  • They don’t work on global variables
  • Wrappers can add indirection, making debugging slightly harder
  • Codable compliance for wrapped properties requires manual implementation
  • Wrappers are executed at runtime, not compile time

You should use them for behavior reuse and encapsulation—not to hide complex logic or side effects.


Making Wrappers Codable-Compatible

To make your custom Swift property wrapper codable, you need to implement Encodable and Decodable conformance manually. Swift doesn't automatically synthesize encoding for wrappedValue.

Example:

@propertyWrapper
struct Uppercase: Codable {
    var wrappedValue: String

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

Then, the containing struct must implement its own Codable logic if needed.


Summary

A Swift property wrapper lets you add reusable behavior to properties in a clean and declarative way. With features like wrappedValue, projectedValue, and custom initializers, wrappers encapsulate logic like value transformation, validation, state tracking, or synchronization. Using a property wrapper Swift developers can avoid duplication and improve maintainability while writing expressive and readable code.

Mastering property wrappers is essential for modern Swift development, especially in SwiftUI and codebases that prioritize modular design. Whether you're implementing lightweight transformations or powerful abstractions, custom wrappers are a flexible tool worth having in your Swift toolkit.

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