How to create Property Wrappers for reuse code
Table of Contents
The first time I encountered a property wrapper was in SwiftUI, but it’s a feature of Swift that you can use since version 5.1.
Properties define characteristics of types, but often you have to perform (the same) operations on them.
The simplest way to perform these operations is by using property observers, which I mentioned earlier.
But what if you need to perform those operations too many times? If you only use observers, you’ll soon have duplicated code.
The solution is to use property wrappers.
Property Wrappers in Swift #
Property wrappers add a layer of separation between the code that defines a property and the code that manages how it is stored.
Their main advantage is that you write the code that operates on that property once, and then reuse it in the properties you need.
How to Create a Property Wrapper in Swift #
In the following code, you can see how a property wrapper is created:
@propertyWrapper
struct DashCase {
private var text = ""
var wrappedValue: String {
get {
text
}
set {
text = newValue.lowercased().replacingOccurrences(of: " ", with: "-")
}
}
}
You can create a class, a structure, or an enumeration, and it must be preceded by the @propertyWrapper
attribute directive.
The type must have a property called wrappedValue
, which will return the value you define when a property with the created property wrapper is accessed.
Thus, we could use the previous property wrapper as follows:
struct Branch {
@DashCase var name: String
}
var branch = Branch()
branch.name = "Random Branch Name"
print(branch.name) // Prints "random-branch-name"
This way, Branch
does not need to format the name each time. Additionally, you can use DashCase
whenever you need to format text to lowercase and hyphens.
struct File {
@DashCase var name: String
}
var file = File()
file.name = "Random File Name"
print(file.name) // Prints "random-file-name"
Note that if you declare an additional property in the type, it is advisable to declare it as private. This ensures that in the implementation, the value can only be accessed through the
wrappedValue
.
Setting Initial Values for a Property Wrapper #
Additionally, if needed, you can use an initializer.
This allows you to create the property wrapper in the following way:
@propertyWrapper
struct SnakeCase {
var wrappedValue: String
init(wrappedValue: String) {
self.wrappedValue = wrappedValue.replacingOccurrences(of: " ", with: "_").lowercased()
}
}
This can be an advantage, for example, when you need to make it mandatory to create the object with an initial value, as you will have to provide one.
struct Table {
@SnakeCase var name: String
}
var table = Table(name: "hello World")
print(table.name) // Prints "hello_world"
Projected Value of a Property Wrapper #
In addition to the wrapped value that a property wrapper returns, as seen in the previous examples, it also allows for a projected value.
This projected value could be used, following the previous examples, to determine if the value has been formatted.
@propertyWrapper
struct Capitalized {
private var text: String
private(set) var projectedValue = false
var wrappedValue: String {
get {
text
}
set {
text = newValue.capitalized
}
}
init(wrappedValue: String) {
self.text = wrappedValue.capitalized
projectedValue = true
}
}
With this, you could do the following:
struct CustomText {
@Capitalized var title: String
}
let text = CustomText(title: "One more thing...")
print(text.title) // Prints "One More Thing..."
print(text.$title) // Prints "true"
Conclusion #
As you can see, using property wrappers brings the main advantage of reusing code across parameters of different types that need to perform the same operations.
The most important points:
- You need to use the
@propertyWrapper
directive, and it must have at least one property calledwrappedValue
. - You can use a
projectedValue
property if you want to expose additional functionality. - If you need an additional property to perform operations, declare it as private so that it can only be modified from within the property wrapper.
If you want to practice, here is a playground with examples 😉