Swift
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Optionals
-
Section 4: Object-Oriented and Value Types
-
Section 5: Memory Management
-
Section 6: Generics and Error Handling
-
Section 7: Concurrency
-
Section 8: Working with Collections
-
Section 9: Codable and Data Handling
-
Section 10: Protocol-Oriented Programming
-
Section 11: Testing and Tooling
-
Section 12: Practical Projects
-
Section 13: Interview Practice
-
Section 14: More Practice Exercises
-
Section 15: More Standard Library
-
Section 16: Advanced Concurrency
-
Section 17: Foundation Framework Deep Dive
-
Section 18: URLSession and Networking Deep Dive
-
Section 19: Combine Framework
-
Section 20: SwiftUI Fundamentals for Swift Developers
-
Section 21: Server-Side Swift with Vapor
-
Section 22: Swift Package Manager Deep Dive
-
Section 23: Swift Concurrency Deep Dive
-
Section 24: More Language Features
-
Section 25: Error Handling Deep Dive
-
Section 26: Testing Deep Dive
-
Section 27: Data Structures and Algorithms in Swift
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Swift Macros (Swift 5.9+)
-
Section 31: Property Wrappers Ecosystem
-
Section 32: Swift Interop Deep Dive
-
Section 33: iOS App Architecture Patterns
-
Section 34: Performance and Debugging
-
Section 35: App Distribution and CI/CD
-
Section 36: More Practical Projects
-
Section 37: SwiftData and Persistence
-
Section 38: More Design Patterns
-
Section 39: More Review and Practice
-
Section 40: More Foundation Deep Dive
-
Section 41: Advanced Collections in Swift
-
Section 42: Advanced Generics Practice
-
Section 43: UIKit for Legacy and Hybrid Apps
-
Section 44: watchOS and visionOS Development Basics
-
Section 45: More Networking Patterns
-
Section 46: More Testing Practice
-
Section 47: Accessibility in Swift Apps
-
Section 48: Localization
-
Section 49: More Practical Projects Round 2
-
Section 50: Swift Charts Framework
-
Section 51: More Interview and Algorithm Practice
-
Section 52: Final Practice and Mastery
-
Section 53: Swift Compiler and Build System
-
Section 54: More Concurrency Practice
-
Section 55: App Store Guidelines and Review
-
Section 56: More Design and Architecture
138: Building a Custom @Clamped Property Wrapper
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
@Clampedproperty wrapper as shown in the lesson. - Create a
Thermostatstruct with a property calledcurrentTemperature. - Apply the
@Clampedwrapper tocurrentTemperatureusing the range5.0...30.0. - Write a small test script that attempts to set the temperature to
-10.0and45.0, then print the results to verify that the values were clamped correctly.
There are no comments for now.