Skip to Content
Course content

138: Building a Custom @Clamped Property Wrapper

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're installing a physical volume knob on a piece of hardware. You don't want the user to be able to rotate the knob 360 degrees and snap the internal wiring, so you install physical "stops" at the 7 o'clock and 5 o'clock positions. No matter how hard the user tries to twist the knob past those points, the physical constraint keeps the value within a safe, predictable range.

In Swift, a @Clamped property wrapper is exactly that: a physical stop for your data. Instead of writing if value > max { value = max } every single time you update a variable, you bake that constraint directly into the property's definition. The property itself becomes responsible for its own boundaries.

Defining the Boundary Logic

To make this work, we need a struct that conforms to the property wrapper pattern. I usually start by making the wrapper generic. Why? Because you'll want to clamp Ints for ages, Doubles for opacity, and maybe Floats for coordinates. As long as the type conforms to Comparable, we can check if it's "less than" or "greater than" our limits.

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

    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        // We clamp the initial value immediately so we start in a valid state
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }

    var wrappedValue: Value {
        get { value }
        set {
            // This is where the "physical stop" happens
            value = min(max(newValue, range.lowerBound), range.upperBound)
        }
    }
}

Notice the min(max(...)) pattern. It's a concise way of saying: "Take the value, ensure it's not lower than the minimum, and then ensure that result isn't higher than the maximum." It's a bit of a brain-bender the first time you see it, but it's the standard idiom for clamping in most languages.

Applying Constraints to Real Data

The real beauty here is how this cleans up your call sites. I once worked on a project where every single setter for a "User Preferences" class had a five-line if/else block to make sure the font size didn't go to 0 or 1000. It was a nightmare to maintain.

With our wrapper, the implementation becomes a one-liner. Here is how you'd actually use it in a game character or a UI settings object:

struct PlayerProfile {
    @Clamped(1...100) 
    var health: Int = 100
    
    @Clamped(0.0...1.0) 
    var volumeLevel: Double = 0.5
}

var player = PlayerProfile()

player.health = 150 // This will be clamped to 100
print(player.health) // Output: 100

player.volumeLevel = -0.2 // This will be clamped to 0.0
print(player.volumeLevel) // Output: 0.0

A Note on the Initializer

You might have noticed I added a custom init. By default, property wrappers can be tricky when you want to pass in a range (like 1...100) during declaration. By defining the init(wrappedValue:range:), we allow Swift to map the @Clamped(1...100) var health = 100 syntax correctly. Without this, the compiler would get confused about which value is the initial state and which is the constraint.




📋 Practical Task

Implementing a Room Temperature Controller

You are building a smart-home app. You need to create a Thermostat struct that manages a room's temperature. However, for safety reasons, the thermostat must never be set below 5.0°C (to prevent pipes freezing) or above 30.0°C (to prevent overheating).

Your Task:

  • Implement the @Clamped property wrapper as shown in the lesson.
  • Create a Thermostat struct with a property called currentTemperature.
  • Apply the @Clamped wrapper to currentTemperature using the range 5.0...30.0.
  • Write a small test script that attempts to set the temperature to -10.0 and 45.0, then print the results to verify that the values were clamped correctly.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.