Dev SwiftUI Apps - 3. Define Data Model

Oct 26, 2020 03:00 · 3325 words · 16 minute read go head valid rating lectures

Welcome back to the Start Developing SwiftUI Apps tutorial series, I am Apollo from UWAppDev, the Mobile Development Club at University of Washington Today we’re gonna learn how you can define your own data model Firstly, we will figure out how and why we are creating a new data model and then we are going to learn how to represent and handle the case of nothingness and thirdly we’re going to use a tool to ensure that our program is working as intended So let’s think about the concept of meal first. For each meal, we’ll have a name, and a rating and optionally a photo so the user knows which meal they are talking about And right now we’re using three separate variables to represent these three properties of a meal mealName, which is a String; mealRating, which is an Integer and in the future we’re going to have a mealPhoto, which is an Image However when we think of it, meal is really just this single entity that we want to represent Is there a way for us to encapsulate all three of these variables into a single thing? That’s what we’re going to do today: we’re going to define this meal entity and we’re going to use it So let’s go back to Xcode and we’re gonna create a new file Since we’re no longer handling some UI code we’re gonna choose this Swift File and click Next We’re just gonna save the file with all our other files But rename it Meal since we’re saving meal in there Now here, since Image is a part of SwiftUI, we’re also going to import SwiftUI So, to define a new meal data model, what we’re actually gonna do is pretty similar to what we have been doing so far We say struct Content View, so similarly we’re gonna say struct Meal and the braces. note that we no longer have the “: View” since the meal is not a view so we’re just going to leave it as is and then we’re going to define its properties including its name which is going to be a String as we have learned before we’re going to have rating which is going to be an Integer and we’re going to have a photo which is of type Image you know previously I have said that a user can choose to include or not include a photo so it might have an image or not And how do we represent that? We add a question mark after the type Image to notify swift that we may or may not have a photo and that’s it as simple as that we just have a struct with the name of the data model you want to define and you include all the variables you want to associate with that data type and put them all inside the curly braces Now let’s go ahead and use our new data model in the ContentView.swift so instead of having separate variables to represent a meal we’re going to just write a single meal of type Meal and we’re going to say equals to Meal, parenthesis, because we want to specify the name, rating, and the photo I’m just gonna say name is the same as empty meal and we have a default rating of four how about photo? what do we use to represent a photo that does not exist well actually, in Swift, this is called nil so basically, if you have something that’s optionally there you use the word nil to represent “oh actually i don’t have (a photo) yet” so now swift is no longer happy because we removed the other variables that represent a meal separately how do we now access a meals name? it’s actually pretty simple. Instead of doing that, we say meal and dot we use dot to access a property of a data model say dot name And that similarly here we have meal dot name in here we have meal dot rating as simple as that we can resume and check our program to see if it’s actually looking exactly the same as before indeed it is: we still have our meal name we still have our ratings however, let’s think about what’s a valid meal Can meal have an empty name like we have right now? I would say no.

It’ll be really hard to order a meal if we don’t know its name 04:55 - and it will be very hard for a restaurant to figure out what did this customer order if they don’t have a name for each of their meals and can we have a negative rating for a meal, like negative one? our program does not allow you to input some negative rating so I would say we won’t accept any negative ratings for meals Now, we’ll go ahead and implement those checks to ensure our meals are actually valid within our program let’s go back to the Neal.swift file how do we add checks? Actually we cannot do it with the default constructor that Swift provides us to initialize a meal so instead, we’ll need to take care of assigning each of these variable values ourselves as you can see here, Swift already generated a default constructor that just assign each of our properties a value that we provides it So we’re just gonna do exactly that. To do so, you say the word init to say we want to initialize this and we’re just gonna say name is of type string we have rating of type Int and we have photo that’s Image question mark or an Optional Image now notice we use the same name inside here which is parameters and we also have the same name as one of our properties if you just say we want our name to equal to the name whoever is using our Meal provides us it’s actually gonna do nothing because it’s not gonna be able to differentiate and Swift is not happy with that To fix this issue we’re just gonna say self dot name equals to name so what self does is, it refers to one of the struct’s properties here instead of one of the arguments here so this name in green refers to this name up here, and this name in black refers to this name in argument. We’re going to similarly do this for each of our other properties and self.photo is equal to photo After we’ve done that Swift is now happy and our code compiles like it used to do now so now let’s go ahead and add the checks let’s say if a meal’s name is empty so, if you have learned some other programming languages you might want to say if name’s number of characters, in Swift number of characters is called “count” is less than or equal to zero, or rating is less than zero, we do something else we go ahead and initialize all our properties and what do we exactly do here? hmmm…

if the user provided us with some arguments that does not make sense 08:00 - what do we want to do with our meal Well, remember this question mark, or an Optional value that may or may not exist we can also do that for initializes we just need to add a question mark here to represent that hmmm… this initialization process may or may not succeed and inside here, if the parameter a user provided us does not make any sense we’re just going to say return nil, to say that if this happens our constructor cannot provide you with a valid meal with all these properties. Instead we’re going to give you nil to indicate that no such meal can exist. And just another thing that computer scientists like to do is if you can guarantee that your if branch is gonna end and do nothing else we can safely remove this else statement and let everything out so we can save a level of indentation, everything looks nice because in the if branch already returned nil, it’s not going to go ahead and do any of the following lines And just a quick thing, since the photo by default is going to be nil we can actually just say that by default photo is going to have the value nil and then we can safely remove this photo: nil from our constructor and our program is still going to run as usual however there’s one additional thing that’s happening: because now meal is optional we may or may not have a meal from this. And in this case, we’re actually not gonna have a meal because the name is empty.

So, instead we’re going to say meal should be initialized with 09:55 - a default nail which is “Untitled Meal,” and since we know that we’re definitely going to have a meal, we can say exclamation mark and force unwrapped optional, saying that I guarantee you, Swift, it’s okay, I know we are gonna have a valid meal from this constructor however, be cautious when you are using the examination mark because if the Optional value you are trying to unwrap is actually nil your program is going to crash so think twice before you use an exclamation mark to unwrap any Optional value to better understand what Optional value can trust and what are some other data you cannot trust, there’s a great talk from WWDC18 about this topic, and you should check it out now to ensure our constructor, this init, that I told you is gonna fail if the user provides us with some name or rating that’s not making sense, it’s going to fail, just me telling you should not convince you it actually does that so we’re going to do is to write some tests so when we’re creating the project, we actually create a unit test for this project and we’re going to open the FoodTrackerTests.swift file you’re going to see some template methods here we don’t really care about them we’re going to remove all of them but pay attention to this line of code, that’s this func test, you’ll notice that there’s this little triangle at the left that you can run this function to test everything, let’s click that after a while you’re going to see that test succeeds and you will have a green check mark next to it so basically whenever you see func test something that is a piece of code that’s going to be ran by xcode and it’s going to test whatever is inside it to make sure our program is working as intended so now it’s just going to delete all of them and we’re going to write our own tests so func let’s say testMealInitializationSucceeds these are the cases that we know our meal initializer is gonna work so we know that a meal with rating of zero should work we’re just going to say variable mealZero is equal to a meal that has an namem let’s say Zero and a rating of 0. and because this should work, we’re going to go head and say XCTAssertNotNil and say mealZero, to check it’s actually not nil we have an actual meal from the constructor you can see now swift is not quite happy with our code here. It says variable mealZero was never changed consider using let instead. So basically let is just another way for you to declare variables but if you say let, it ensures that that variable cannot be changed.

So for example here, we have 13:12 - var meal, that is why we are able to change its name we’re able to change its rating. However here since we’re not changing any of the meal’s properties, we can go ahead and use the keyword let instead of var to declare a variable that’s not going to change. And it reads naturally let meal with a rating of zero to be this meal with name zero and rating zero. And we’re going to go ahead and add another meal that’s going to have a rating of five. mealFive equals to a Meal and our left parenthesis and we’re going to choose this, how about this one we’re going to have a name that’s called Five and have a rating of 5, and it’s going to have again nil as its photo, just a reminder you can use nil to represent something that’s optional and not present.

And again we can see that mealFive is optional, so we’re going to assert 14:11 - it’s not nil with XCTAssertNotNil, and we’re going to say mealFive should not be nil So if we run the test now, this should succeed. And indeed it does, and now let’s check our initializer actually fails when it should, so we’re going to add another func test and we’re going to say testMealInitializationFails. In this case, we’ll have meal with an empty name, equals to Meal we’re just going to pass an empty String rating let’s just make sure it does not interfere with our checking logic we’re going to pass it something like a 1 or a valid rating and in this case we want to assert it’s actually nil, so we say XCTAssertNil instead of NotNil emptyName. And let’s say a negativeRating for Meal I’m gonna say negative, yeah we’re passing a negative rating of, let’s say, negative five how about that. It really can be any negative number so again in this case we want to make sure it’s nil because it’s invalid parameter and we say negativeRating, want to ensure it is nil oh and I remember our rating only had five stars, so it’s impossible to have a 10 star review let’s just test that Meal let’s call it “6 Stars” and have a rating of six, I think this should fail us, right? Because it really have too much stars associated with that meal, and I don’t think that makes sense so now let’s check if this test actually succeeds, and the answer is no.

Because currently 16:22 - our initializer does not return nil when we have meal that has a rating of six, we just checked if it’s negative or not. So let’s go back and change our meal to handle the case when rating is actually higher than max possible. Though we can go ahead and add another check here I feel like this is really crowded and we should separate it out so I’m gonna say if our name is empty I’m gonna return nil here and if rating is less than zero or if rating is greater than 5, I’m going to return nil so great we have now checked for all cases that we should return nil. However Swift provides a stronger way to ensure that if some precondition is not met, you must do something to handle it and we can do that using guard statements So guard statement allows you to check for some preconditions so instead of we say name.count is less than or equal to zero, we want to really say name should not be empty, so name.

count should be greater than zero, else we’re going to return nil 17:45 - and let’s just quickly see that what happens if we don’t return nil here Swift is going to complain that guard body must not fall through, so we must do something catastrophic in here, which is return nil in this case, And for rating instead of saying what we have right now, we should say guard, it should be in this range of zero to five, just like what we have been doing rating stars control, and it should really contain our rating, else will return nil or if you don’t like this you can always also say our rating should be greater than or equal to zero and it should be less than or equal to five. These two ways of writing this are exactly the same, it’s just your own preference, and i just want to show you different ways to write the same statement however here, we say name.count is greater than zero There’s actually a better way to represent this idea what we want to do here really is to check if this name string is empty, and surprisingly Strings actually just have a property called isEmpty so we can just say if the name is not empty I know this is confusing, we’re using exclamation mark again, but for some condition you are checking inside if or guard statement, adding an exclamation mark in front of it means “not.” So we’re really saying here: guard the name is not empty then we proceed otherwise, we’re going to return nil because the name is empty so now we have revised this part we can go ahead and go back just click this one with the class it’s going to run the entire test suite and hopefully yes our tests are passing now so as a summary, today we have seen how you can create new structures to define your own data model how you can add properties to your data model to associate with that idea and how you can write your own initializer instead of using the one that Swift provides you by default we have seen that how you can use Optionals and use nil to represent that this thing does not exist and how you can convert your initializer to be failable and return nil instead of constructing the data model if the user provides you some data that’s not gonna work and then we have seen how you can use control flows like using if if else or guard else to check for the conditions and we have also seen how you can write unit tests using XCTest to ensure that your program is working as you expect it to. To really ensure that you actually understood the concept instead of just listening to our contents, we feel like providing you with some thoughts or actual code experiments may help you better grasp the ideas that we talked about so as the homework, you don’t have to do them but with strong encourage you to, is that you can define a data model for the concept of students.

We suggest you to include properties like student id 21:00 - their name, and a photo. You can go ahead and add other properties that belong to a student and I know the concept of Optional is really hard to grasp when you first encounter it, I had a lot of trouble understanding the concept of Optionals so let’s think about this weird situation our meal initializer currently checks to see if the name is empty, and if the name provided is empty it’s going to return nil for that meal. And we have used exclamation mark after meal constructor to force unwrap it because we are very confident that the meal we constructed will not be nil at any time. However we are able to edit the name of a meal when we delete every single character inside the input text box for the meal’s name. Our program actually does not crash you can run the app right now and check if that’s the case and I ensure you it is.

You might find it strange 22:05 - how is our app not crashing. This will be an interesting question to figure out so if you have any questions about the lectures or if you want to check with us about your homework please feel free to email us at appdev@uw.edu or contact us through other social media platforms we have listed our website uwapp.dev, and see you next time .