Last two articles (article 1 and article 2) focused on property wrapper basics.
In this article:
- We will convert DevTechieDiscount property wrapper from previous article to a generic minMax property wrapper.
- We will also develop a new property wrapper for data validation.
- Learn limitations of property wrappers
Generic Property Wrapper
As we have seen so far, property wrappers are special cases of types(struct or class). This mean we can convert our property wrapper to generic so it can be used to any types that meets criteria defined by the property wrapper.
Let’s take a look at example, we will change DevTechieDiscount ‘s name to MinMax .
@propertyWrapper
struct MinMax {
var min: Int
var max: Int
var value: Int
init(wrappedValue: Int, min: Int, max: Int) {
value = wrappedValue
self.min = min
self.max = max
}
var wrappedValue: Int {
get { value }
set {
if newValue > max {
value = max
} else if newValue < min {
value = min
} else {
value = newValue
}
}
}
}
This is great for finding out min max but at this point our implementation is limited to Int type only. We would like to change that and make it generic so as long as value type conforms to Comparable protocol, we can find min and max for that type.
@propertyWrapper
struct MinMax<T: Comparable> {
var min: T
var max: T
var value: T
init(wrappedValue: T, min: T, max: T) {
value = wrappedValue
self.min = min
self.max = max
}
var wrappedValue: T {
get { value }
set {
if newValue > max {
value = max
} else if newValue < min {
value = min
} else {
value = newValue
}
}
}
}
With this change let’s apply this to our Course type to try it out:
struct Course {
var name: String = ""
var price: Int = 10
@MinMax(min: 1, max: 50) var discount: Int = 10
func getFinalPrice() {
print("Final price: ", price - price * discount / 100)
}
}
We will create course object and print discounted price:
var course = Course()
course.name = "SwiftUI in Depth"
course.price = 30
course.discount = 10course.getFinalPrice()
getFinalPrice will print following:
Final price: 27
Let’s change Course ‘s discount to Double :
struct Course {
var name: String = ""
var price: Double = 10
@MinMax(min: 10.5, max: 75.5) var discount: Double = 10
func getFinalPrice() {
print("Final price: ", price - price * discount / 100)
}
}
We will create course object again:
var course = Course()
course.name = "SwiftUI in Depth"
course.price = 30
course.discount = 10course.getFinalPrice()
This time the output we will get will be
Final price: 26.85
Notice that final price has changed because discount set in object is 10% but min supported discount will be 10.5% so discount will change to 10.5 and final price will be computed as 30–30 * 10.5 / 100 = 26.85
We can see that we didn’t have to re-create property wrapper for Double values. This implementation will work for strings as well 🙃.
Data Validation using Property Wrapper
For our next example, we will move on to sign in page for DevTechie Courses app. For sign in scenario our model will have to verify email and phone number for user which are perfect candidates for property wrappers.
First we will create a RegExValidator which will validate both email and phone number for us(or any other regex worthy validation 🤩)
@propertyWrapper
struct RegExValidator<T: StringProtocol> {
var value: T?
var regEx: String
init(regEx: String, wrappedValue: T) {
value = wrappedValue
self.regEx = regEx
}
var wrappedValue: T? {
get {
validate(value: value) ? value : nil
}
set {
value = newValue
}
}
private func validate(value: T?) -> Bool {
guard let value = value else { return false }
let predicate = NSPredicate(format:"SELF MATCHES %@", regEx)
return predicate.evaluate(with: value)
}
}
DevTechie Login model will look like this, notice our validator and regex patterns.
struct DevTechieLogin {
var name: String
@RegExValidator(regEx: "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}", wrappedValue: "")
var email: String?
@RegExValidator(regEx: "(\\+\\d{1,2}\\s)?\\(?\\d{3}\\)?[\\s.-]\\d{3}[\\s.-]\\d{4}", wrappedValue: "")
var phone: String?
}
Let’s put test code to check both valid and invalid cases:
Valid case:
var signIn = DevTechieLogin(name: "DevTechie")
signIn.email = "example@example.com"
signIn.phone = "999-999-9999"print(signIn.name, signIn.email ?? "--Invalid--", signIn.phone ?? "--Invalid--")
Output:
DevTechie example@example.com 999-999-9999
Invalid case:
var signIn = DevTechieLogin(name: "DevTechie")
signIn.email = "exampleexample.com"
signIn.phone = "9999999999"print(signIn.name, signIn.email ?? "--Invalid--", signIn.phone ?? "--Invalid--")
Output:
DevTechie --Invalid-- --Invalid--
Limitations of property wrappers
Property wrappers are awesome and when used at the right place can make code much easier to maintain and efficinet but like everything else, there are few things even property wrapper can’t do:
- Properties can’t have multiple property wrapper applied to them
- Property wrappers can’t throw errors so error handling is not possible.
With that, we have reached the end of this article. Thank you once again for reading, if you liked it, don’t forget to subscribe our newsletter.