# Swift Property Wrappers: A Practical Guide to @propertyWrapper

Source: https://devdocs10.com/articles/swift/swift-property-wrappers  
By Murat Bo · Updated 2026-10-17 · DevDocs10 (https://devdocs10.com)

Swift property wrappers let you attach reusable logic to a property so you can validate, transform, observe, or store values in a consistent way. They are a language feature, not a framework feature, and they help remove repetitive code from models, view state, and data validation.

> **Quick answer:** A property wrapper is a type marked with `@propertyWrapper` that defines how a property gets and sets its value through `wrappedValue`. Use it when the same storage or validation logic would otherwise be repeated across many properties.

> **Difficulty:** Intermediate
>
> **You'll understand this better if you know:** basic Swift properties, structs and classes, and how getters and setters work.

## 1. What Is Swift Property Wrappers?

A property wrapper is a reusable type that sits between a property and the code that reads or writes it. Instead of putting validation or conversion logic directly into every property, you define that behavior once in a wrapper and apply it with an attribute such as `@Clamped` or `@Trimmed`.

- They are declared with the `@propertyWrapper` attribute.
- They control a property's storage through `wrappedValue`.
- They can expose extra information through `projectedValue`.
- They are useful for validation, defaults, formatting, caching, and observation.
- They reduce duplicated accessor code in models and APIs.

Think of a property wrapper as a small reusable layer around a property value. The wrapped property still looks like a normal property at the call site, but the wrapper handles the implementation details.

## 2. Why Swift Property Wrappers Matter

Without wrappers, repeated property behavior often turns into duplicated `didSet` logic, custom getters and setters, or helper methods scattered across your types. Property wrappers make that behavior composable and easier to read.

They matter because they improve:

- **Consistency:** the same rules are applied everywhere the wrapper is used.
- **Readability:** the property declaration shows the intent clearly.
- **Reuse:** one wrapper can serve many properties.
- **Encapsulation:** validation and storage details stay inside the wrapper type.

They are especially helpful when you want a property to always stay within a valid range, automatically trim text, supply a default value, or publish changes to observers.

## 3. Basic Syntax

A property wrapper is a type that defines at least a `wrappedValue` property. You apply it to another property with the wrapper name prefixed by `@`.

### Minimal wrapper example

The following wrapper stores an integer and prints it back unchanged. It is intentionally simple so you can see the moving parts clearly.

```swift
@propertyWrapper
struct Logged {
    var wrappedValue: Int
}

struct User {
    @Logged var score: Int
}

var user = User(score: 10)
user.score = 20
```

This code creates a wrapper type named `Logged`, then uses it on `score`. Swift stores the value through the wrapper, but the property still reads and writes like a normal `Int`.

### How wrappedValue works

The `wrappedValue` property is the value exposed to code outside the wrapper. When you read `user.score`, Swift gives you the wrapper's `wrappedValue`. When you assign to `user.score`, Swift updates that same value.

## 4. Step-by-Step Examples

### Example 1: Clamping a number

A common use case is forcing a value into a safe range. This wrapper ensures a volume value always stays between 0 and 100.

```swift
@propertyWrapper
struct Clamped {
    private var value: Int
    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)
    }
}

struct PlayerSettings {
    @Clamped(0...100) var volume: Int = 150
}

var settings = PlayerSettings()
settings.volume = 120
```

The initial value and later assignments are both clamped. That means `volume` never leaves the allowed range.

### Example 2: Trimming text input

Wrappers are often useful for cleaning user input before it enters your model.

```swift
import Foundation

@propertyWrapper
struct Trimmed {
    private var value: String

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

    init(wrappedValue: String) {
        self.value = wrappedValue.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

struct Profile {
    @Trimmed var username: String
}

var profile = Profile(username: "  alice  ")
```

The wrapper keeps whitespace out of the stored value, which helps avoid subtle bugs in comparisons, storage, and validation.

### Example 3: Providing a default value

A wrapper can also provide a useful default when no explicit value is passed.

```swift
@propertyWrapper
struct DefaultFalse {
    var wrappedValue: Bool = false
}

struct FeatureFlags {
    @DefaultFalse var isEnabled: Bool
}

let flags = FeatureFlags()
```

This example shows a wrapper with a built-in default. The property can be declared without an explicit initializer at the use site.

### Example 4: Accessing projectedValue

Some wrappers expose extra information through `projectedValue`. By convention, that value is read through the `$` prefix.

```swift
@propertyWrapper
struct LoggedChange<Value> {
    private var value: Value

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

    var projectedValue: String {
        "Current value is set"
    }

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

struct AuditRecord {
    @LoggedChange var message: String = "Saved"
}

let record = AuditRecord()
let status = record.$message
```

The projected value is a second interface for the wrapper. In real code it might expose a binding, metadata, validation state, or a helper object.

## 5. Practical Use Cases

- Validating form values such as email addresses, usernames, or numeric limits.
- Normalizing text by trimming whitespace or standardizing case.
- Applying safe defaults to configuration and feature flags.
- Wrapping persistence logic, such as storing values in user defaults or a cache.
- Exposing auxiliary state through `projectedValue` for debugging or binding.
- Reducing repeated boilerplate in models with many similar properties.

Use property wrappers when the rule belongs to the property itself, not just to a single method call. If the logic should always apply whenever the value changes, a wrapper is often a good fit.

## 6. Common Mistakes

### Mistake 1: Forgetting to provide a wrappedValue initializer

Swift needs a way to construct the wrapper when you use it on a property. If the wrapper stores data but never defines how to initialize `wrappedValue`, property declaration can fail or become awkward.

**Problem:** The wrapper type has storage, but the compiler cannot match the wrapped property to a valid initialization path.

```swift
@propertyWrapper
struct Title {
    private var value: String
}

struct Book {
    @Title var name: String
}
```

**Fix:** Add an initializer that accepts `wrappedValue` and stores it in the wrapper.

```swift
@propertyWrapper
struct Title {
    private var value: String

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

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

struct Book {
    @Title var name: String
}
```

The corrected version works because Swift can now create the wrapper from the property's initial value.

### Mistake 2: Expecting projectedValue to exist automatically

The `$` accessor only exists when the wrapper defines `projectedValue`. New Swift users sometimes assume every wrapped property automatically has a projected value.

**Problem:** Accessing `$property` on a wrapper that does not define `projectedValue` results in a compile-time error.

```swift
@propertyWrapper
struct ClampedInt {
    var wrappedValue: Int
}

struct Settings {
    @ClampedInt var count: Int = 1
}

let settings = Settings()
let info = settings.$count
```

**Fix:** Define `projectedValue` if you want the `$` syntax.

```swift
@propertyWrapper
struct ClampedInt {
    var wrappedValue: Int
    var projectedValue: String {
        "The value is " + String(wrappedValue)
    }
}

struct Settings {
    @ClampedInt var count: Int = 1
}

let settings = Settings()
let info = settings.$count
```

The corrected version works because the wrapper now explicitly exposes the projected value.

### Mistake 3: Using a wrapper when the value should not be transformed

Wrappers are powerful, but they should not hide surprising behavior. If a property needs to preserve exact input, automatic changes such as trimming or clamping may be the wrong choice.

**Problem:** The wrapper silently changes the value, which can lead to confusing bugs when the stored value does not match the input you passed in.

```swift
@propertyWrapper
struct Lowercased {
    private var value: String

    var wrappedValue: String {
        get { value }
        set { value = newValue.lowercased() }
    }

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

struct Tag {
    @Lowercased var label: String
}

let tag = Tag(label: "SwiftUI")
```

**Fix:** Use a wrapper only when the transformation is a true rule of the domain, or remove the wrapper and store the exact value directly.

```swift
struct Tag {
    var label: String
}

let tag = Tag(label: "SwiftUI")
```

The corrected version works because the property stores the value exactly as provided, without hidden conversion.

## 7. Best Practices

### Practice 1: Keep wrapper behavior small and focused

A wrapper should usually do one job well. If it starts validating, logging, caching, and formatting all at once, it becomes hard to reason about and harder to reuse.

```swift
import Foundation

@propertyWrapper
struct Trimmed {
    private var value: String

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

    init(wrappedValue: String) {
        self.value = wrappedValue.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}
```

This wrapper is easy to reuse because its responsibility is clear and limited.

### Practice 2: Make the transformed behavior obvious in the property name

If a wrapper changes input values, the property name should make that behavior predictable. This helps prevent surprises for other developers reading the model.

```swift
struct Account {
    @Trimmed var displayName: String
}
```

Here, `displayName` is a reasonable place for trimming because the name suggests a user-facing string, not an exact raw input record.

### Practice 3: Prefer initialization that preserves invariants

Ensure the wrapper's initializer enforces the same rules as later assignments. Otherwise, the property can start in an invalid state and only become valid after the first update.

```swift
@propertyWrapper
struct Clamped {
    private var value: Int
    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)
    }
}
```

This keeps the property valid from the moment it is created, which is especially important for model integrity.

## 8. Limitations and Edge Cases

- Wrappers add a layer of abstraction, which can make debugging slightly less direct if the behavior is not documented clearly.
- A wrapper is not always the best choice for one-off logic that only applies to a single property.
- Wrapped properties still obey the normal rules of the enclosing type, including access control and mutation rules.
- If a wrapper is applied to a computed property, the design is usually wrong; wrappers are meant for stored properties.
- Using wrappers on value types and reference types can feel different when the wrapper itself stores mutable state.
- Some wrapper designs expose additional capabilities through `projectedValue`, but not every wrapper needs that complexity.

One common surprise is that the wrapper's own storage and the wrapped property's visible value are not necessarily the same conceptual thing. The wrapper may keep private state, while the wrapped property exposes only the public interface.

## 9. Practical Mini Project

Let's build a small profile model that uses two wrappers: one trims names and one clamps age to a safe range. This is a realistic pattern for user input coming from a form or import step.

```swift
import Foundation

@propertyWrapper
struct Trimmed {
    private var value: String

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

    init(wrappedValue: String) {
        self.value = wrappedValue.trimmingCharacters(in: .whitespacesAndNewlines)
    }
}

@propertyWrapper
struct Clamped {
    private var value: Int
    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)
    }
}

struct Profile {
    @Trimmed var displayName: String
    @Clamped(0...130) var age: Int = 0
}

var profile = Profile(displayName: "  Sam  ", age: 200)
profile.displayName = "  Taylor  "
profile.age = -5
```

If you inspect `profile` after these assignments, the name will be trimmed and the age will be clamped into range. This is a compact example of how wrappers keep model rules close to the data they protect.

## 10. Key Points

- Property wrappers move repeated property behavior into a reusable type.
- The wrapped property is exposed through `wrappedValue`.
- The `$` prefix reads `projectedValue` when the wrapper defines it.
- They are best for rules that should always apply to a property's value.
- Clear naming and simple behavior make wrappers easier to maintain.

## 11. Practice Exercise

- Create a wrapper named `NonEmpty` that stores a `String`.
- Make it replace an empty string with `"Unnamed"`.
- Use it in a `Contact` struct for a `nickname` property.
- Print the stored nickname after initialization and after assigning an empty string.

**Expected output:** the nickname should never be empty.

**Hint:** Put the normalization logic in both the setter and the wrapper initializer.

**Solution:**

```swift
@propertyWrapper
struct NonEmpty {
    private var value: String

    var wrappedValue: String {
        get { value }
        set { value = newValue.isEmpty ? "Unnamed" : newValue }
    }

    init(wrappedValue: String) {
        self.value = wrappedValue.isEmpty ? "Unnamed" : wrappedValue
    }
}

struct Contact {
    @NonEmpty var nickname: String
}

var contact = Contact(nickname: "")
print(contact.nickname)

contact.nickname = ""
print(contact.nickname)
```

Output (tested with Swift 6.4):

```text
Unnamed
Unnamed
```

## 12. Final Summary

Swift property wrappers are a clean way to package repeated property behavior into a reusable type. They are most useful when a property should always follow a rule such as trimming, clamping, defaulting, or exposing related metadata.

To use them well, keep each wrapper focused, make its effect easy to understand, and initialize values in a way that preserves the same rules you apply later. When used carefully, wrappers make your Swift code shorter, safer, and easier to maintain.

If you want to go further, next study `projectedValue` patterns, wrapper composition, and how property wrappers appear in SwiftUI state types.
