'How can I have more than 1 propertyWrapper for a variable in Swift?
I have some propertyWrapper that I want to apply them to a variable. Below is my code:
@propertyWrapper struct Capitalized {
var wrappedValue: String { didSet { wrappedValue = wrappedValue.capitalized } }
init(wrappedValue: String) {
self.wrappedValue = wrappedValue.capitalized
}
}
@propertyWrapper struct Trimmed {
var wrappedValue: String { didSet { wrappedValue = wrappedValue.trimmingCharacters(in: .whitespacesAndNewlines) } }
init(wrappedValue: String) {
self.wrappedValue = wrappedValue.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
Use Case:
@Capitalized @Trimmed var testValue: String = " hello, world! "
Errors:
Cannot convert value of type 'Trimmed' to specified type 'String'
Composed wrapper type 'Trimmed' does not match former 'wrappedValue' type 'String'
How we can have more than one propertyWrapper? As you can read the question I looking to have more than 1 propertyWrapper, hopping it is clear to understand it.
Solution 1:[1]
The issue is that the type of the wrappedValue given to Capitalized ends up being a Trimmed, and not the String that's within the Trimmed.
You can see that for yourself by building a little debugging wrapper:
@propertyWrapper struct InspectType<T> {
var wrappedValue: T
init(wrappedValue: T) {
self.wrappedValue = wrappedValue
print("The type of the `wrappedValue` is: \(T.self)")
}
}
struct Sample {
@InspectType @Trimmed var testValue: String = " hello, world! "
}
_ = Sample() // prints: "The type of the `wrappedValue` is: Trimmed"
You need to generalize your Capitalized to handle both a String, and another "String-like" thing. I think LosslessStringConvertible seems like a decent choice to describe a "String-like" thing, so I'll go with that. Here's how I would do this:
@propertyWrapper struct Capitalized<S: LosslessStringConvertible> {
private var storage: S
public var wrappedValue: S {
get { storage }
set { storage = S.init(newValue.description.capitalized)! }
}
init(wrappedValue: S) {
self.storage = S.init(wrappedValue.description.capitalized)!
}
}
Which gives us this error:
error: generic struct
Capitalizedrequires thatTrimmedconform toLosslessStringConvertible
Which makes sense, because in the compositional case of @Capitalized @Trimmed var testValue, the property wrappers will have a nest type like Capitlaized<Trimmed>. Naturally, we just need to define that conformance:
extension Trimmed: CustomStringConvertible {
var description: String { self.wrappedValue }
}
extension Trimmed: LosslessStringConvertible {
init?(_ string: String) {
self.init(wrappedValue: string)
}
}
Now it works!
struct Sample {
@Trimmed var trimmedString: String = " hello, world! "
@Capitalized var capitalizedString: String = " hello, world! "
@Capitalized @Trimmed var testValue: String = " hello, world! "
}
print(Sample().testValue) // prints "Hello, World!"
You might notice however, that the inverse composition doesn't work:
struct Sample {
// ? error: cannot convert value of type 'Capitalized<String>' to specified type 'String'
@Trimmed @Capitalized var inverted: String = " hello, world! "
}
If we want this to work, we need to do the same generifying of Trimmed. Here's how that might look, all together:
import Foundation
@propertyWrapper struct Capitalized<S: LosslessStringConvertible> {
private var storage: S
public var wrappedValue: S {
get { storage }
set { storage = S.init(newValue.description.capitalized)! }
}
init(wrappedValue: S) {
self.storage = S.init(wrappedValue.description.capitalized)!
}
}
extension Capitalized: CustomStringConvertible {
var description: String { self.wrappedValue.description }
}
extension Capitalized: LosslessStringConvertible {
init?(_ string: String) {
self.init(wrappedValue: S(string)!)
}
}
@propertyWrapper struct Trimmed<S: LosslessStringConvertible> {
private var storage: S
public var wrappedValue: S {
get { storage }
set { storage = S.init(newValue.description.trimmingCharacters(in: .whitespacesAndNewlines))! }
}
init(wrappedValue: S) {
self.storage = S.init(wrappedValue.description.trimmingCharacters(in: .whitespacesAndNewlines))!
}
}
extension Trimmed: CustomStringConvertible {
var description: String { self.wrappedValue.description }
}
extension Trimmed: LosslessStringConvertible {
init?(_ string: String) {
self.init(wrappedValue: S(string)!)
}
}
struct Sample {
@Trimmed var trimmedString: String = " hello, world! "
@Capitalized var capitalizedString: String = " hello, world! "
@Capitalized @Trimmed var testValue: String = " hello, world! "
@Trimmed @Capitalized var inverted: String = " hello, world! "
}
print(Sample().inverted)
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Alexander |
