Custom Swift Property Wrappers

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hi my name is stuart lynch if you're familiar with swift ui you'll have heard of property wrappers in swift ui we have at state at binding at state object and many more property wrappers when dealing with representation in state regeneration of our views i have a complete video on these swift ui property wrappers but in this video we're going to be looking at how you can create your own custom property wrappers and learn about two special properties the wrapped value and the projected value and how you might want to use them in both ui kit and swift ui implementations if this is something you want to learn then keep watching i've created a starter multi-page playground project that you can download from the link in the description below there are two pages besides the introduction with some sample code in each i'm taking an idea from ray wenderlich and including with each page a helper file that will keep each of the examples contained within a closure and this means that we'll be able to recreate the same struct for multiple examples as the code will be isolated within a closure and will not conflict with other examples even if we use the same struct names it also has the benefit of keeping our print statements separated you really don't have to worry about this all we have to do is make sure that any code we want to isolate within an example we keep within the closure so let's get started on the first page we'll be looking at the use case for property wrappers a property wrapper is the swiss language feature that allows us to define a custom type that implements behavior from get and set methods and reuse it everywhere let me show you what i mean by that let's say for example that you have a struct called name and two properties first name and last name both strings we want to ensure that whenever a new instance of name is created both the first and last names are always capitalized and if we do this right now let's enter a new instance and print out the first name and last name as expected we get exactly what we typed the first and last names printed out so how do we go about ensuring that when they are printed the first and last names are always in caps and there are a couple of ways to do this and one would be to add two computed properties that would return the upper case versions of these two variables well that's not the approach i want to take instead before we look at property wrappers let's take a look at using a property observer on each property so that when we set a new value we apply the upper case function to it so for first name we'll specify did set with first name equal first name dot uppercased and we'll do exactly the same for the last name did set last name equals last name dot uppercased running this now shows a problem and that is that it doesn't capitalize on initialization the memberwise initializer of our struct doesn't trigger the deadset function if we change one of the properties after the initialization like our first name and run it again we see that that updated property does get capitalized as expected so to solve the problem on initialization we'll need to add our own initializer where we'll do the same did set transformation when the instance is initialized this time it works of course if this were a class we would have to have provided the initializer anyway now that's a lot of code to write and what if you had lots of properties that you wanted to apply that transformation to well this is where property wrappers become very helpful a property wrapper has two minimum requirements you create a class a struct or an enum that is decorated with the app property wrapper annotation so let's call ours all caps now every property wrapper requires a property called wrapped value that is of the same type as the property that we want to decorate so in our case that would be a string with that created we create a property observer on our wrapped value that will perform the same transformation that you require which in our case is to uppercase the string now if you're performing a transformation on that value then you'll want to also ensure that there's an initializer for the wrap value that gets that same transformation applied to it well now that we have that created we can decorate any of the properties that we want to upper case with the at all caps property wrapper annotation that we just created well this is the same struct that we had in our first example so let's try that same instance for creation and printing executing the playground now the problem solved well now that we know that this works if this were a full xcode project and not a playground we could have moved this property wrapper struct here into its own file for use in any class or struct that we have in our project let's take a look at a second example and this is a pretty common use case in your app you have a model with a number of string properties like this one and you want to ensure that when an instance of the struct is created there are no leading or trailing spaces right now if i create an instance of the struct like this with some leading and trailing spaces around our first and last name properties and then try and print that out we'll see that our output maintains those spaces i'd like to have those spaces trimmed and this is another great use case for property wrappers so let's do that now again we'll start with at property wrapper for our struct and i'll call it trimmed and i'll create the required wrap value property and the type will be a string so instead of using the did set property observer i'm going to use a getter and a setter on our wrap value this will require the introduction of a private string property that i can manipulate within the struct and then let me create our set and get blocks now when we set our wrap value it generates a new value called new value and we can assign that new value to our private text property and then for the getter instead of returning the wrap value we'll return the text but apply the strings trimming characters function with the white space and newline's character set we'll also need an initializer here for our wrap value and we'll assign the initial value of wrap value to our text property let me decorate my string properties now with the trimmed property wrapper and when i execute the block the strings are trimmed let's look at another example this time with the struct exam that has two properties a student as a string and a score as an integer let's create an instance of this struct for a physics exam and give it the name stuart with a score of 120 and then if we want to print out the score we can do that too now the problem that i have here is that i want to cap the exam score at 100 and also make sure that there are no negative scores i.e none below zero well we can use a property wrapper for that so let's call this property wrapper in range and add our wrap value which this time is an int i'm going to take the same approach as we did in the last example i'll create a private property called score and use a getter and a setter on the wrap value and i'll also need an initializer for that wrap value like the previous example when we set our wrap value we set the score with that new value and when we get our value we first apply the transformation on the score and what we'll do is we'll return the max value of 0 and the minimum of our score and one hundred essentially what this does is restrict any values to be between zero and one hundred and of course in the initializer we simply initialize our score with the wrap value so now we can decorate our score property with the in range property wrapper and running this we now see our score is capped at one hundred if we change the score to minus five the minimum value of zero is provided instead now this is a pretty restrictive property wrapper i'm always restricted to exams with scores between 0 and 100. what if i have other exams and i want a different range in order to do that we have to add some properties to our property wrapper so let me copy and paste what we have now into the next example and make some modifications let me add two more private properties to our property wrapper one and int for minimum score and another one for max score now i can replace my hard-coded values in the getter this also means i have to add two more arguments to the initializer and this creates an error and it shows up when we are creating our instance the issue however arises from the property wrapper however it is a struct with two new properties and we need to have initial values this shows that we also need an initial value for our wrap value too well we can set our minimum score to say 10 and the maximum to 80 but what do we do about the wrap value well the wrap value is a computed property and it will change to whatever we set our score to on the initialization so we can basically assign any integer that we want and when we create an instance of the struct it will change this still gives us an error however but remember that the new wrap value is actually going to be assigned the value of our score so we can leave it out of the argument entirely and set an initial value here for our score we could use any int value because it will be overridden when you create a new instance of the exam for example let's set our physics score back to 120 and if we run this same playground now we'll see that the score is maximized at 80. if we change the score to 5 and execute again it's set at 10 our minimum now one last thing before i move on to projected values this property wrapper is restricted to integers if we want to use it for all numeric types we can use generics so one more time let's copy the entire solution from the previous example and paste it into the next slot all we have to do is add a generic t assignment to our property wrapper and also specify that it must be numeric then we can replace all of our int types with t now this almost works but it displays one more error in that our type must also be comparable so we need to add that to our type definition this still works with the existing code but we can also use double values because they're numeric and comparable so let's change our minimum score to be 0.5 and our maximum to be 1.0 and then of course we'll have to remove the designation for our variable and it can be inferred from our range that it will be a double so we can just leave any type designation out and we'll just set our default value as 0. now let's adjust our physics score to be a value of 1.5 and when we execute the app we see it's restricted to 1. if we reduce the score to 0.2 and execute the playground again we're presented with a minimum of 0.5 the only values that will work are those between 0.5 and 1 inclusively property wrappers can also have a projected value this means that we can create another representation of the property and it can be a different type entirely for example let's assume that you live in the u.s and you always format your dates as month day and year and you want to be able to decorate any date with a property wrapper so it will always give you that formatted date when you request it now what i would normally do is create an instance of our struct and i'll give the date for the birth date and i'm just going to use today as the date next i would create a deep formatter instance and then set the date format of that instance to my required format like this next i'd use the string from date function on date format to print out the date this gives me the desired result but what if i had lots of dates i want to keep this code out of my views and make it reusable so let's do this using a property wrapper with a projected value so as before we'll create a property wrapper and we'll decorate it with the property wrapper designation and we'll call it usd and include the required wrap value which will be a date this time now we're not going to manipulate that wrap value which is assigned to our property but rather return a string that will be the usdate formatted version so for this we can create what is known as a projected value and it can be a computed property i can use that same date format string from our previous example and then return a string value using the wrap value of our property wrapper which is the underlying date value now no initializer is required for this example now i can decorate our birthdate property in our person struct with that property wrapper now in order to access that projected value of a property vapper we'll use the dollar prefix on its property so if i want to print out the string version of the birthday i'll just print me dot dollar birthday of course we'll always have access to the original date value we'll just leave the dollar sign off now let's improve on this by making it a bit more generic first let's copy the code from that previous example paste it into the next part and i'll change the property wrapper to formatted date and i'll change the decoration on our struct as well next i'll add a new string property for our property wrapper called format and replace the string with that variable adding a new property means that we'll have to create an initializer and we'll also have to include the wrap value as well here now this means we'll also have to add a formatted argument to our formatted date so let's specify eee comma mmm space d comma yyyy and as we saw with wrap value we can set an additional date for the wrap value and of course when the instance of person is created we can override that date with the real one running this now we get our desired result for our formatted birth date now if we want that format to be the default and allow for it to be optional to change the format we can remove that format string directly into our property wrapper's initializer and with it removed from the property wrapper we get the same result but this does give us the option to add dna formatter if we choose so how about yyyy-mm-dd running once more we get our desired result for our final example i'd like to consider this struct that can be used as a review for a restaurant i want to allow for a review of the general ambiance and another for the food delivered so i'll create an instance of this for one of my favorite restaurants earl's and give it a five rating for ambiance and 4 for food now i'd like to create a property wrapper that i could use to assign to our two integer properties a projected value that would show a custom emoji repeated equal to the number of times it matches our rating value for example 5 stars for ambiance and 4 emojis of some other type for the food rating so let's create a property wrapper that will do that for us we'll start as always by decorating our struct with at property wrapper and i'll call my struct rating and add the required wrap value which is an int the projected value is going to be a string but i also want to be able to provide an emoji for that rating image so let's create both of those properties for the projected value we'll just return the string repeating the emoji string for a count of the wrap value also as before this will require an initializer for the wrap value and the emoji string so let's default it to the star let me decorate the two properties in our struct using the default star as our ambiance emoji so no need to provide an argument to the property wrapper but for our second rating let's use an emoji for food how about this plate now we get that error saying we need an initial wrap value so as before we'll just set it value to some random integer knowing both will change when our instance is created now we can print using the dollar prefix to represent the projected value and there you have it all you'll need to do if you want to use these property wrappers in your own projects is to create a new file in your projects and copy and paste these structs into that file i hope that you see the power behind property wrappers notice that apart from the introduction there's been no mention of swift ui property wrappers are pure swift and can be used to decorate properties in your classes and structs in both ui kit and swift ui projects i have lots of other videos available and in the queue as well so please check out the rest of my channel you can also visit my website to see the apps that i have available on the app store and visit my github page to see what i have available as public repositories if you like what you've seen give it a thumbs up and subscribe to my channel and ring the bell to get notified when i post new videos i'm most active on twitter so please follow me there as well to find out what else i'm up to thanks for watching
Info
Channel: Stewart Lynch
Views: 1,305
Rating: undefined out of 5
Keywords: Swift, Property wrappers, Custom, Xcode, SwiftUI
Id: AXfSE2ET8c8
Channel Id: undefined
Length: 23min 59sec (1439 seconds)
Published: Sun Dec 27 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.