Lecture 2: MVVM and the Swift Type System

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
(mellow ambient music) - [Announcer] Stanford University. - [Instructor] Right, well, welcome back to lecture two of Stanford CS193p in spring of 2020. I'm gonna dive right back into the demo that we started in lecture one, however, first, I'm gonna cover these two really important conceptual ideas. First is MVVM. This is a design paradigm we're gonna use to design our app, kind of organize our code and then the second thing is I'm gonna talk about the type system in Swift. So let's do this MVVM thing first. MVVM is a code organizing model. Basically, a place to determine where all your code lives in your app and it works in concert with this concept of reactive user interfaces that I mentioned last time. It has to be adhered to for SwiftUI to work, this MVVM. You can't do SwiftUI without it. And for those of you have seen this class in previous quarters, this is different from MVC, which is Model-View-Controller that the UIKit, the old-style iOS development mechanism uses. Okay, so MVVM, it shares a lot with MVC in that we're trying to separate the Model, which is our back-end of our app, right? The UI independent part with the View, which is what's in front of the users. Let's talk about the Model and the View first and then we'll talk about how MVVM hooks them up together. So the Model is UI independent. The Model doesn't import SwiftUI, for example. It is trying to encapsulate the data and the logic about what your application does. So in the case of our card matching game, this is the cards, that's the data, and it's also the logic, what happens when I choose a card, how do I match, how many points do I get when I match, what happens if I have a mismatch. All of that logic and the card data lives in the Model. The Model is the truth, okay? For all that data and logic, it's the truth. We're never going to store that data somewhere else and have two different versions of it. We're always gonna go to the Model for the truth. Now, the View reflects the Model. The data is always flowing from the Model to the View. We're always going to try and make our View look just like our Model. However our View draws what's in the Model, so, however our card matching game appears on screen, it's always going to reflect the state of the game in the Model. So that's important thing to understand about the View. Always reflecting what's in the Model. The View is pretty much stateless because all the state about the game is in the Model. So the View itself doesn't need to have any state. The View essentially just takes whatever the current state of the Model is and displays it and it should be able to do that at any time, just at any time should be able to say to View, look at the Model and look like that right now. And that's the way we're gonna design our View. And that makes the View what we call declarative. Declarative means we're just going to declare that the View looks this way and we're only going to actually change anything on screen when the Model changes. If you look at the code that we wrote last time, notice we don't call functions to put things in places, we just create RoundedRectangles and Texts and HStacks and ZStacks and things so that we just create them and place them where we want them in the UI. The only functions we call in this code that we wrote are modifiers, things to change the look of things and they're doing it right in place. So, while all this code that we wrote yesterday is just declaring what our user interface looks like. Now, that's different than the old way of doing iOS apps and also a lot of other systems that have been around for years, which we would call imperative. So if you hear this imperative model of doing user interface or coding in general, think of imperative, the same root as the word imperial, right? An imperial state is one in which an emperor rules and the emperor goes around saying, oh, you do that and build this and then plant these fields, and so, he's telling people what to do, he's the emperor and that's how the country gets run. Well, to use that metaphor in the UI world, you're saying put this button here and arrange these things on UI over there and you're calling functions to do this over time. So, why is the imperative model kind of bad for UI? Well, the main reason has to do with time. These things, these functions are called over time. Put the button here and then later, we're gonna arrange this over here and then later, something's gonna happen here. So if you want to understand how your UI is built, you need this other dimension of time to know when this function can be called and what function call depends on some other call happening first and then once your UI is up and running, someone could call a function to change your UI at any time so you have to kind of be always on guard and ready for that. Well, that is kind of a nightmare to manage and almost impossible to prove that your UI really works because you can't call every function in every possible order, so it just doesn't make any sense, whereas declarative, you can always look at what you've declared for your UI and see this is what this thing does. At anytime, it's really time independent, it should be able to at any time be asked to do what it does, to draw what it does and it's just going to look like the code you're looking in front of you. It also localizes all the code. So all the code to draw your UI all right in front of you. That code we wrote yesterday, that was it, that was the entire code for showing the UI of our cards and there wasn't some code some other where else that was gonna call a function on this and mess it up. In fact, you're gonna learn later today that structs, our Views are structs, they're actually read-only by default. No one is allowed to call a function that would change it. It's not even possible to do. So that way, you can be sure that this View is always gonna look like exactly what you see in the code that you've declared right in front of you. As huge advantage is for understanding how your code is gonna be working, making sure that you, and that random things aren't happening as your application runs. It's fantastic. Big improvement over imperative models for UI. And then the last part of the View is it's going to be reactive. That means that anytime the Model changes, it's going to automatically update the View 'cause I told you the View is stateless. At any time you should be able to say make it look like the Model and so, we are gonna have a system where whenever the Model changes, it asks the View to look like it automatically. Call that reactive programming, it's reacting to changes in the Model. So that's it. This is what we got to make happen is it introduces another thing called the ViewModel. The ViewModel's job is to bind the View to the Model. So the one change has happened in the Model, the View get reflected. And along the way, as it does that binding between the Model and the View, it might be interpreting the Model for the View because we want the View to be very simple since we're writing it in a declarative way, we don't want it to have a lot of code in there that's like converting from one data type to another and things like that. So we're gonna ask the ViewModel to do that. Our Model in our game that we're writing here, this memory game, our Model is just gonna be a struct. This is a very simple little demo but you could imagine that your Model is a SQL database or it's something over the network where you're making HTTP requests. So it can be quite complicated over there and your ViewModel can simplify that, boil it down into maybe some simpler data structures that it can pass to the View that will let the View be simple code that draws it. So, the ViewModel does have this role a little bit as an interpreter of that Model data. So we're gonna interpose the ViewModel between the Model and the View. It's gonna be the thing that helps make this automatic updating happening. So how does it do what it does? Well, first of all the ViewModel is always trying to notice changes in the Model and it can do that any way that it wants. If your Model is a struct, it's actually quite easy. Again, I'm gonna talk about a struct, more about that type in Swift in a few moments but one great feature of a struct since it's copied around when it's passed to functions and stuff is that Swift knows when a struct has changed. It can track when a struct changes and so it's very easy for a ViewModel whose Model is a struct to see when it's changing. But if the Model were a SQL database, I don't know how much you all know about databases but it's quite easy to insert things into SQL database so that when it changes, you get notified. But it's up to the ViewModel to know these changes in the Model. That's one of the primary things it has to be able to do. Now, when that data changes, it might interpret that data, it might convert it to some other format or something like that but then what it does is it publishes something changed to the world, okay? To anybody who's interested. That's all it does, publishes something changed. It doesn't actually have any pointers to any Views, okay? The ViewModel never has a pointer to its View. This is an important thing to understand. The ViewModel does not talk directly to its Views. When things change in the Model, it publishes something changed. But then the View subscribes to that publication and when it sees that something has changed, it goes back to the ViewModel and asks, okay, what's the current state of the world? And I'm gonna draw myself to match that state of the world. And the reason it asks the ViewModel that, it doesn't go directly back to the Model, is because the ViewModel might be doing this interpreting for it or it might be protecting the Model to make sure that some nefarious View doesn't do something bad to the Model. This is how this whole thing works, it's as simple as that. ViewModel notices changes in the Model but anytime something changes, it says, oh, something has changed and then the Views just observe those something changed things happening and then it pulls the data from the ViewModel and redraws itself because that's what a View can do, can always redraw itself with the current state of the Model which it gets it through this ViewModel interpreter. This is all there is to it and as the quarter goes on, we're gonna see the Swift syntax for making this all work. I've put some of the syntax up here like ObservableObjects and onReceive, and objectWillChange going on, these are all things which I'm gonna start talking about next week. Actually, we're gonna see it right at the end of the demo today even while we're gonna start using some of these keywords to make this whole thing happen. Now, what about the other direction? We've talked about how the Model can be flowing into the View and the View is always reflecting what's in the Model. What if the View, which is where buttons are and swipe gestures are happening, what if it wants to change the Model, okay? How does that work? Well, for that, we add another responsibility for the ViewModel which is for it to process Intent and by Intent, I mean the user's intent, what the user intends, the actual end user. And this is MVVM, this whole system. There's another somewhat related architecture called Model-View-Intent, okay? Which makes even more clear that when the user wants to do something, they go through this Intent. Now, Apple's iOS SwiftUI design does not implement an Intent system so I'm just gonna talk about Intent as a concept here. An Intent is some user intent. A classic example here, in our memory game, the user is going to have the Intent of choosing a card. That's their intent. So it's up to the ViewModel to process these Intents and it does this by making functions available to the View to call to make the intent clear. So the View, whenever a gesture happens, tap gesture, swipe gesture or whatever, the View is going to call an Intent function in the ViewModel. And it's just a documentation thing. We're gonna have a section in our ViewModel's code that's got a comment at the top, these are the intentions or the Intents of the end user. And that makes it really clear what can happen, what the user can do that will change the Model. Now, when the ViewModel receives a function like this called on them, they're gonna modify the Model and again, the ViewModel knows all about the Model and how it's represented, if it's SQL, it's gonna be issuing SQL commands to change the Model, if it's a struct, then maybe it's just setting vars or calling functions in the Model to modify. you can do whatever makes sense to express that user's Intent in changing the Model. So now the Model is changing. What happens next? Well, the exact Intent we talked about before. The ViewModel notices the change that it just made, it publishes something changed and then the View sees that something changed and it automatically redraws itself. And this is it, this picture, this whole picture you see right here, this is the MVVM architecture, these are the Swift keywords that you're gonna see in the code when we write to make this all happen but it's as simple as this. And really, the key to all this is just understanding each of these three thing's roles because they're gonna be very clearly defined in the code. So, in the demo that I'm gonna do today, we're going to implement this MVVM architecture for our memory game We couldn't make our memory game work really any other way. If we did, it would be extremely bad. I'm not sure how we could do it but if we could make it work somehow, that would be wrong. We wanna use MVVM. All right, before we do that though, let's have another, cover another little topic here which is types. So there's a lot to learn about the Swift programming language but we're gonna start with learning about the kinds of types it has and it has these six types struct which you've already seen. Class which is for object or your programming. We'll see that. Protocol, which, actually, you've also seen that. Don't care types, okay, which we call generic, so the generic system. enums and functions. Yes, functions are types in Swift. But in the interest of time, I'm only gonna cover these four of the types: struct, class, these don't care types and functions. I'll get to protocol and enum next time. All right, let's start with struct and class. Struct and class look almost exactly the same. Their syntax is very, very similar. They both have stored variables like var isFaceUp that we saw in the demo from last time. They also both can have computed variables like we saw from the demo last time, right? Var body. Its value is computed each time someone asked for classes and structs. Both can have that. They can also have these things called lets. Okay, a let is just a var where the var does not actually vary. It's not variable, it's a constant. So, a let is essentially a constant. They can both have constants in them. They also both have functions. We haven't talked a lot about the syntax of functions so let me take just a moment here to talk about that. We do already know that for a function, the arguments have labels or in this multiply function, it's got two arguments. The first argument is called operand and the second one is called by, out of both of type Int, this function returns an Int and inside of multiply. I just use the labels operand and by to make it operate. so if I say multiply operand five by six, that's obviously going to return 30 for us. I do wanna tell you that those labels, actually, each parameter can have two labels. So here, I have multiply again but each of them has two labels. I have the first parameter has the label underbar and the label operand and the second one has the label by and the label otherOperand. See how there's two, a blue one and a purple one for each of the arguments and why are there two? Well, the blue ones are used by callers of the function and the purple ones are used inside the function. So the purple ones look the same as our previous one, return operand times other operand. That's the second of the two labels for each of them but look at the caller. He now says multiply five by six. So the underbar label means no label. That's why we've seen things like text, which takes a little emoji string, it doesn't have to have a label there, it's because it's using this underbar somewhere in its code to mean you can leave it out. Underbar always means leave it out, okay? Unused, you wanna think of it that way, it's the unused character in Swift. We'll see that in the demo today as well. And then the second one still uses by as the external name. We call that the external name of this, and it's using otherOperand as the internal name. So you can stare at this a little bit later but this is the syntax, basically, for functions and functions exist in both structs and in classes. Now, structs and classes also have special functions called initializers. And initializer are used to create your struct or class with some argument that is not one of your variables. We've already seen like with CardView and remember CardView, we created it with the argument isFaceUp true and it set the var isFaceUp in our CardView. So we can always initialize things that way but what if we want to use some other kind of argument to get something initialized? And a great example it's gonna be our MemoryGame because when you create a MemoryGame, it's vars are gonna be like it's cards or things like that but really, a MemoryGame, when you create it, you wanna say how many pairs of cards are in the game. Is this a big MemoryGame with 20 cards or a smaller MemoryGame with only six pairs of cards or something like that? So, I wanna be able to have the argument to creating a MemoryGame be numberOfPairsOfCards which is an Int and I can do that by putting an init function inside my MemoryGame, which takes that as an argument. And the cool thing is I can have any number of these inits that I want, each of which taking a different argument. Maybe there's other ways to create a MemoryGame. So structs and classes both have initializers. So what's the difference then between structs and classes? They looked awfully similar and they are roughly similar but there are some fundamental differences so let's talk about what those differences are. The biggest difference is that struct is a value type and class is a reference type. So let's talk a little bit more about what that means. Value type versus reference type. A reference type is passed around by pointers. Reference types live in the heap. Okay, so classes, when you create them, the storage for them is in the heap. Everyone knows what that means. That's just like stored in memory and when I pass it around, I'm passing around pointers to it. So a lot of people might have a pointer to the same class somewhere. Structs are not passed around by pointer, they are copied. So if you pass a struct to a function as an argument, that function gets a copy of it even if I just have one variable and I have another variable that I said equal to the first variable, both variables are a separate copy of it. This might seem like, whoa, you're kidding me. I'm making copies of, I mean arrays are structs so I'm making copies of huge arrays every time I pass it to a function or something like that? And the answer is, of course, that's not actually happening. Behind the scenes, Swift is, when you're passing these things around and it's copying these structs, it is not really copying the bits of it, it's somehow sharing them until you then try to write to it. So if you pass an array to a function, it might copy that into another variable and then it wants to add something to that array, then it's gonna make a copy, an actual bitwise copy of the array so that you can add to it because the one you added it to is a different copy than the other one. So that's called copy-on-write, when you actually write to a struct, it actually really makes a copy. But semantically, every time you pass struct around, it's getting copied. It's just always copied. So you're not sharing, these structs, as you pass them around, are never shared, they're copied. Now, a class, on the other hand, you're passing pointers to it, so instead, what it's doing is counting the references. Seeing how many pointers there are to this thing and this happens automatically and when finally, no one is left pointing to the classes in the heap, then the memory gets freed up out of the heap. So that's called automatic reference counting. So this is two very different ways of thinking about the world, right? Copying it as you pass it around or by pointer. Most things that you see are structs. So arrays, dictionaries, Ints, Bools, Doubles, those are all structs. And struct is kind of basically built to support a kind of programming called functional programming. Functional programming focuses on the functionality of things. Classes are built for object-oriented programming. Object-oriented programming focuses on encapsulating the data and the functionality into some container, okay? An object. These are very different kind of concepts of the world. They're both trying to achieve similar goals, which is encapsulation a little bit and also just understanding where the functionality lives in your program but they're doing it quite differently. And you can tell by the way their types are built. Copying it around versus having a pointer to it lead to a lot of different behavior and over the course of this quarter, we're going to learn a lot about functional programming and how it works even in the rest of this little lecture, we're gonna understand a lot more about that. I'm assuming you all pretty much know about object-oriented programming. You programmed in Java or C++ or something so you kinda know. Structs have no inheritance. That's not really, that wouldn't make any sense, really, in functional programming to have inheritance. We do have a kind of inheritance in functional programming, you'll see, but not with structs, okay? Struct can't have inheritance. And classes in Swift do you have inheritance. Of course, they can have a superclass if they want but it's single inheritance. They can only inherit from one class. That's what you're used to and Java only has single inheritance, C++ a single inheritance, et cetera. So that's a big difference as well. I told you about those init functions. In a struct you get a free init that initializes all the vars in your struct. Get that one for free, that's why we're able to say CardView isFaceUp colon true and it initializes that isFaceUp var that we had there because we got a free initializer that initialize all the vars. In a class, you also get a free initializer but it doesn't initialize any of the vars. It's always open parentheses, close parentheses, that's the free one. So that would mean all of your vars would have to have equal something after them or you have to provide your own init in a class. So in classes, we almost always are providing inits. Because of this reason, we don't get a nice free one. In structs, it's a mix. Sometimes, we do the CardView way of using the free init, sometimes, we create our own init. In value type programming, we're copying these things around mutability or changeability has to be explicitly stated with a struct. If you have a struct and you want, like it's an Array, and you wanna be able to add items to it, like it wants to be a changeable Array, then you have to explicitly say that that's what you're gonna do and you do that by using var versus let. Remember I said that structs and classes both have this thing let which is a constant? Well, if you say let a variable equal a struct, then you can't mutate it, you can't, if it's an Array, you couldn't add items to it but if you say var something equals a struct, now, you can. Whereas classes are always mutable. They live in the heap, you have a pointer to them, you could always go through that pointer and modify the thing in the heap, okay? There's no control of mutability in a class, which is really a bigger, when it comes to trying to build code, did you understand what it really is doing, provably doing? That's a big problem with classes, that anybody who has a pointer to a class can just go mutate it. It's like that's so Wild West, it's really hard to understand what's going on. So having this mutability be explicit, it's really nice feature in functional programming and structs. Now, structs are your go-to data structure. You're pretty much gonna use struct's as your first try. You're only gonna resort using a class in certain specific circumstances. And we're gonna see one of those circumstances today, which is your ViewModel. The ViewModel in an MVVM is always a class. Also the old way of doing iOS programming, that was all class-based, that was all object-oriented, not functional programming. Why is the ViewModel in MVVM a class? By the way, look, I'm gonna talk about this in the demo but it has to do with the fact that a ViewModel needs to be shared amongst a lot of different Views, perhaps. The ViewModel is kind of the portal on to the Model. A lot of different Views might wanna be looking at that Model and they wanna share that portal. Classes are great for sharing because we all have a pointer to them. There's a downside on sharing, we try to mitigate that in MVVM, I'll show you that in the demo as well but that's an example of a class. Pretty much everything else you've seen, it's been a struct. All these Views that obviously you've seen, they're all structs. I said, Arrays, Ints, Bools, Doubles, everything, all at range, everything is a struct pretty much, except View. View is actually a different type called a protocol. A View is not a struct or a class, it is a protocol and we'll be talking about protocols in great detail next week. Okay, the next thing I wanna talk about is generics. We may want to manipulate some data structure that we're kind of type agnostic about it. We don't really care about the types, like whatever. Give me whatever type but the problem with Swift is it's an extremely strongly typed language, every var, every parameter to every function, everything has to have a type. No such thing isn't really, well, for backwards compatibility to old UIKit, there's kind of is an untype thing. But, really, in Swift, in SwiftUI, for sure, we don't use untyped variables. Variables all have to have types. So, how do we specify this type when we're in a situation where we don't care what the type is? So we have something that we're manipulating but we don't really care what its type is, all right? So, how do we do this? Well, this is best shown by example. One of the best example in the world is Array. An Array contains a bunch of things, that's what an Array is but it doesn't care what type those things are. Inside Array's code though, it's got to store those things, it's got to have some vars or something inside of it. They're storing the things inside of it. So how do we fix this conundrum of where array needs to be storing things but it actually doesn't even care whether it's an Array of Int, Array of Strings, Array of another Arrays, Set of Array, whatever, Array doesn't care. Also, Array has functions and vars on it. They'll let you do things like add to the Array or get the values of the Array. How are those going to declare their return types and their argument types? Well, the answer is you do this with generics. Now, other languages, Java, for example, have generics as well. For some of you, just a little bit of review but we're going to take these generics to the next level next week when we combine them with some of the other type features in Swift. So let's talk about how generics work with Array. Array's declaration looks something like this, approximately. Struct Array, angle bracket, Element and then, for example, the function append takes an argument which is of type Element. This type Element, what is it? It's what I like to call a don't care type. It's a type that Array just made up the name of, that means I don't care what this type is. So it's a don't care type, and Array doesn't care. It could be an Array of Int, Array of String, whatever. This is essentially another kind of type in Swift called I got to call anyway, I don't care type. So now, Array's implementation about append doesn't know anything about the Element's type. It does not gonna send any messages to or access any vars on the element, it's just gonna store it in vars and stuff like that. So Element, is kind of like a placeholder for whatever type that is. When does that type actually get set to be a real type, not a placeholder? Well, that's when people use Array. So if I declare an Array, I can say var a equals Array and now, I put in angle brackets the actual type that Element is. So this is an Array of Ints. Now, it's sort of almost like someone did a search and replace in all of Array's code where they searched and replaced Element with Int. So the function append now takes an Int as an argument. That's why I can say a.append(5) and that works perfectly 'cause five is an Int and append now takes an Int as an argument. So, it's the using of Array when Element's actual type gets determined, when the code in Array is being written, it's all a don't care. It just uses the type Element, which is a don't care. Notice that when you use a don't care like this, you have to let the world know about it because they're gonna have to tell you what it actually is when they use it and that's what the angle bracket Element there at the top is all about. And it's perfectly legal to have multiple don't cares. Struct Array, angle bracket Element, comma, Foo, comma, so you may have as many don't care as you want and then the person who's using it has to specify what all those types are. Okay, I call these don't care types, elements that don't care type. The real name for these is type parameter. And the last type I wanna talk about is functions. So, functions are people too. Functions can serve as a type and the syntax for it is really simple and straightforward. Here's some example of some functions that are types. This in yellow, the stuff you see in yellow, that is a type. Just imagine the yellow part is Int or String, right? Or Array of Int, okay? It's exactly, it's just as much a type as any of those things. So this type is a function that takes two Ints and returns a Bool. That's what that type is. So you could have the parameter to a function of this type. This one is a function that takes a Double and returns nothing. This one is a function that takes nothing and returns an Array of Strings. This one is a function that takes no arguments and returns nothing. These are all just types, each of these yellow things, just a type. Nothing kind of special about them, really. That means I can declare a variable of one of these types. So like let's say I have a variable foo, its type could be a function that takes a Double and returns nothing. Or I could have a function. Do something with an argument what, what to do and it's type is a function that takes no arguments and returns a Bool, presumably, in the body of do something it wants to do that what. So let's see how we use something that's of type function so I'm gonna have here a var called operation of type function that takes a Double and returns a Double. And I'm going to create a function that takes a Double and returns a Double. So I have this function square, takes a operand Double and returns a Double, it squares it, operand times operand. So now, since that's what it is I can say operation equals square, right? Operation of type function that takes a Double and returns a Double equals square. Square is a function that takes a Double, returns a Double. It's perfectly legal and I can then, now that I have operation, I can execute it by saying let result1, let's say equal operation of four. So result1 would be 16 'cause that's what square does. Notice that when I called operation though, I did not say operation operand: The little var thing, that gets dropped, okay? That's one thing happens when you pass something through a function type is it loses its little labels. But I could also come along and say operation equals square root so sqrt is a built-in function in Swift and it square roots a thing, but it's just a function that takes a Double and returns a Double. So I can say operation equals square root. And now, if I say let result2 equal operation of 4, result2 is gonna be two because I changed the value of operation. That used to be a function square and I changed it to be a function square root. It's simple as that. I mean, seems almost too simple to be true but it is what it is. And in the demo that I'm gonna do right in a few seconds here, we're going to create our own little function that takes a function as an argument. You're gonna hear the phrase closures and I'm gonna talk a lot more about closures probably next week or the week after. A closure is essentially inlining a function, taking a function that you're passing to another function as a parameter and you're in lining it instead of having it be separately declared somewhere. It's a little more than just inlining it because it's doing some nice things to capture local variables and things. So, that's what I'm gonna talk about next week but I am gonna show you what the syntax of inlining functions looks like in the demo that we're gonna do. And in fact, that is the end of our slides for today. So this is what we talked about and so now, we're gonna go back to the demo and we're gonna try and talk about everything that I covered in the slides in the demo. Remember that you're gonna have to reproduce this demo for your first homework assignment, which is out there, kind of came out with these lectures so make sure you check Piazza for that. Let's take our Memorize app here to the next level by using this MVVM architecture to give it some brains, some logic and some data being the cards. How are we gonna do this? Well, we've been working so far, all this code that you see here on the screen on the View. So in MVVM, we've been working on the first V, the View and the next piece we're gonna work on is the Model, okay? And again, the Model is UI independent, it's not gonna know anything about how the game is going to be displayed. Anytime we're gonna add a new Swift file to Xcode, that we do that with the File menu under New, File. There's a lot of different kinds of files you can create but really, comes down to these two right here. This creates a new SwiftUI View for you, right? Something that's gonna say struct whatever colon View with var body, all that and this one creates a non-UI, just blank Swift file, which is what we want because our Model is not a UI struct. So let's double click on this. Now, it's asking you where you wanna store it and what you wanna call it. Well, this is gonna be the heart of our memory game so I'm actually gonna call this file MemoryGame because the struct that I'm gonna create inside of it, the main struct, is a MemoryGame struct that plays the MemoryGame. So, what the other thing down here, it's asking is where do you wanna put it? This yellow is the same as this yellow over here and similarly, this blue is the same as this blue up here. We don't really ever wanna put things up here in the blue. We wanna put them in these folders, okay, these yellow folders. Also, no matter which group you choose here, you wanna make sure it's in the same place in a filesystem as things like your content View. You see the content View there, we wanna make sure that's what's selected in here. So let's create this. Here it is, right? MemoryGame. Notice that it does not import SwiftUI 'cause it's not a UI thing. Foundation here, this, I talked about this last time. It has Array and Dictionary and String and Int and Bool and all the basic types but now, it doesn't have View or Text or RoundedRectangle or any of those UI things. So we're gonna create a struct here. Remember, struct as our go-to data structure and I'm gonna call it MemoryGame and it's not gonna have colon View because it's not gonna behave like a View, it's a non-UI thing. And when I create a struct that's gonna represent my Model, by the way, my Model might not be a struct like this, it might be a SQL database or some network thing that I'm getting information from but a lot of times, there's a struct that's at least wrapping around a lot of that stuff. It could also be a class. It's possible in some circumstances to have Models that are classes but you don't get structs as our go-to so we're definitely gonna start with our go-to data structure here. Now, when I create a Model, I'm always asking myself what does this Model do? And let me see if I can get the vars and functions in place that could really describe what this thing does. And so, when I think of a MemoryGame, a card-matching MemoryGame, the most important thing I'm thinking about is gotta have some cards. So I'm gonna have to have some var, which is the cards, and of course, all of ours need a type so we do types with colon something and I think my cards are gonna be an Array. And Array is a generic type, which means it has this don't care type, which in the case of an Array is the type of the thing that is contained in the Array. So, I'm gonna need some type, some real type that is in this Array. So it's gonna make one up, I'm gonna call it Card right here and I'm just gonna go down and say struct Card is some struct of some sort and this struct is gonna have to represent a single card. Notice that I put this struct Card inside of this struct. So the full name of this one is actually MemoryGame.Card. Nesting structs inside structs, it's mostly a naming, a name spacing thing so that we know that this is not a playing card or some other kind of random card, it is a MemoryGames card that's why we put it in here and has some other slight benefits that you'll see along the way. Now, what else does a MemoryGame need beside a bunch of cards? It needs a way to choose a Card. So you're gonna see here your first definition of a Swift function and you do it with the keyword func, of course, and next is the name of the function, I'm gonna call it choose and then any arguments, in this case, you're gonna choose a Card, so I'm gonna put this argument here for card. Now, notice, as promised, almost all arguments to all functions have a label and this makes it so that when callers are calling choose, it's clear that they're calling choose with a card that that is the argument right there. Now, inside here, we are going to have to actually fork off and do all the logic for our game matching cards. For now, I'm actually just going to do, use a print of statements. So, print is a great function in Swift and it prints a string. So here I'm printing an empty string. But I can say something like card chosen and then I wanna put this Card, somehow, in this print right here and in other languages, you might do %s and then put the card out here but in Swift, you don't do that. When you wanna embed something in a string that's of a different type, you actually do backslash, open parentheses, close parentheses and then you can put it in here. And as long as this can be turned to do a string of some sort, then this will work and Swift is amazingly good at turning almost anything into a string. Now, this struct right here it doesn't have any vars right in here so it probably not gonna print my job there, might just say empty struct Card or something like that but we're going to obviously add vars and then when we say card chosen, it's gonna print out what the values of those vars are as long as all those vars can be converted into strings as well. So this is a super powerful mechanism. this backslash, open parentheses, close parentheses. I encourage you to use it, it's great for debugging. You can print things out when things happen. It's awesome. Now, this is a pretty simple function. We're gonna learn over the quarter various pieces of syntax for doing functions. For example, if this returned a value, it would look like this, okay? A little arrow this basically saying coming out of this function is a string but ours does not and if it had other arguments here, might be other argument, might be an Int or something like that. It can have as many arguments as it wants. So, basically, this is our entire MemoryGame right here. Just ask cards, you can choose them. But we're gonna have to really obviously decide what a Card looks like, what's important about a Card and one thing we know a Card has is whether it's face-up or not so I'm gonna say var isFaceUp: Bool. I think, also, I'm gonna need to know whether a Card is matched. Okay, so it's gonna have that Bool as well. And what else is there on a Card? Well, I guess there's the contents of the Card, okay? So, what's on the Card. Now, this is a var. I'll call it content and the question is what type is this var going to be? Now, I could imagine building a card game with images on there. Obviously, we can build a card game with emoji, that's what ours is. You could build a car game that just has words on it. Maybe it's a card game with numbers. So is this an Int or is it a String or is it an image of some sort? It's almost like we don't really care, okay? So, if we're a MemoryGame, you can put anything you want on the Cards. We're doing UI independent game playing so we don't really care what's on the Cards. So this is a don't care, okay? This is a don't care. So I'm gonna call this CardContent. That's a type I just made up, my don't care type and of course, if I do a don't care type, I'm required up here to say CardContent to declare to the world that I'm a generic type and I have this don't care that you, if you wanna use MemoryGame, you're going to have to tell me what this is. Now, in our game, once we start using this Model, we're gonna save MemoryGame, angle brackets string because an emoji is just a character in the string so we're gonna say angle brackets string and that's gonna define what kind of MemoryGame this is. But this is a really awesome simple example of this don't care business 'cause really, this MemoryGame does not care what's on these Cards. All right, now that we have our Model here and we have our View right here, let's do the third piece of MVVM which is the ViewModel. So the ViewModel is going to be the glue that glues this totally UI independent thing to this totally UI dependent thing. So, let's do that by File, New again, we're gonna create a new thing here, so a new file. Okay, it's not a SwiftUI View, it is a UI thing but it's not an actual View, it's the ViewModel. So we're gonna Swift File. I'm gonna call my ViewModel here, I'm gonna call it EmojiMemoryGame because it's a specific kind of MemoryGame that happen to use emoji as the thing it draws. And I'm gonna make sure that it's in the right folder, it's in the same place as all the rest of the stuff. Okay, great. Here it is, EmojiMemoryGame. It's importing foundation but here, I could actually import SwiftUI if I want. I'm not actually gonna do UI in here, I'm going to be doing all my UI over here in my View. But the ViewModel is essentially a UI thing because it knows how this is gonna be drawn on screen. That's in fact some of its purpose in life is to take this UI independent Model MemoryGame and translate it to have it displayed in some way. In this case, as a EmojiMemoryGame. Before we dive into our ViewModel here, let's hide this preView which you can do, by the way, right here. You can see Show Editor Only. Well, hide that. You can always bring it back with Canvas. Okay, and then hide it again. So, let's build our ViewModel here. Now, one interesting thing right off the bat is that I'm gonna make our ViewModel be a class. I'm gonna call it EmojiMemoryGame. By the way it's a class, object-oriented, it could have a superclass here, which we would specify something like this but our emoji game does not have a superclass, okay? And I'm gonna explain in a moment here why this is a class instead of a struct. But let's think about what a ViewModel is, right? We know that it's essentially a portal between the Views and our Model, right? It's the door wait for the Views to get to the Model. So for sure, what our ViewModel needs here is some sort of var that it can access the Model through. Now, I'm actually calling this var model, right? Which you probably wouldn't call any var model because that's a concept but I'm calling it here just for instructor purposes. You'd really probably call this var something like game, something more descriptive of what it is. It's a MemoryGame so you'd probably call it game or memoryGame or something. But I'm gonna call it model so that all the rest of the code, you'll be able to see, oh, he's accessing the Model there. Now, what's the type of our Model? Well, that's this thing we just built over here, this struct MemoryGame, this generic MemoryGame thing that has this CardContent don't care, which is the contents of the Card and our emoji game, of course, the contents of the Cards are Strings, all right? Emojis are Strings. So this type is just MemoryGame where the CardContent is a String, okay? Simple as that. Now, let's talk more about why EmojiMemoryGame is a class and maybe I can even draw an analogy between the ViewModel and the Model that will help understand how these things interact. Now, a class, probably the biggest advantage of a class is that it's easy to share because a class lives in the heap and it has pointers to it. This is what you're used to in object-oriented programming. Well, since it lives in the heap, you can have pointers, all of our Views could have pointers to it and when we start building complicated user interfaces, we're gonna have lots of Views and many of those Views are gonna wanna look through this portal, okay? Which is what a few Model is, a portal on to the Model. You're gonna wanna look through here and see the Model, okay? And they're gonna wanna share it. So it's a really great use of class to have all these Views sharing, they'll each have a pointer to, this one, portal onto the Model. But as with many things, the class' biggest strength is also its greatest weakness, okay? The problem with lots of different people pointing to the same ViewModel here is that if any one of them kind of messes it up, it ruins the party for everybody. And especially in this circumstance. Here's my analogy. Imagine that there's a house, okay? And inside this house are all our Views, they lived there. And this ViewModel right here, EmojiMemoryGame is the front door because essentially, ViewModels are doorways, they're portals for the Views to exit the Model. And so the Model is the outside world, okay? Everything outside the house, that's the Model. So all of our Views will live in the house wanna look through the doorway and they're all sharing it, right? They all live in the house, they're all looking through the same doorway. They all have pointers to that same doorway. If you want think about it, they're huddled around it looking out and that's a good thing because they're all seeing the outside world in exactly the same way through this same doorway, so our UI is always gonna be nice and self consistent. They're all seeing the same thing. Now, there's a big problem with our front door right now is that it's wide open, okay? Our doorway is open. It has this var right here, model, which any of our Views could look at and they could go, for example, find a Card in there and they could set it to be isMatched and that could really mess up our game, okay? And why would that mess up our game? Well, maybe our game keeps track of the score and when Cards are matched, it gives you points or something and if you just went into the Cards and just set isMatched, now, the Card will be marked match but you never got any score change, et cetera, so essentially, that one bad rogue View has ruined the whole game for all the other Views who are all looking at the same thing. So you can see that this open doorway to the Model makes the fact that our ViewModel is a class and this share thing kinda dangerous. But there are some things we can do to mitigate the kind of worrisome effect of all sharing this same class but still have the advantage of them all sharing. And one is we can close the door, okay? So this var, if we mark it with the keyword private, that means that this model, this var can only be accessed now by the EmojiMemoryGame, okay? It is private to this class. Now, this solves that problem of the rogue View going off and setting isMatched in a Card but it kinda solves it too well because now, none of the Views can look out the door, okay? None of the Views can see the Model anymore. The store is closed and the outside world's inaccessible to the Views, all right? So, that is definitely a problem there. So, how can we find a middle ground there? Well, one way we can do that is by using a little different private here called private set. So if we say private set, that essentially like the door is closed but it's a glass door, okay? So private set means only EmojiMemoryGame can modify the Model but everyone else can still see the Model. So this is a glass door. Now, the glass door works great to make sure that the rouge View doesn't go in there and change a Card to be isMatched and doesn't get scored and all that. It fixes that problem but now, nobody can choose any Cards either because we can't get through, the Views can't get through the glass door to choose a Card, for example, that's one of the main things that these Views probably wanna do is tap on a Card, you wanna choose it. And so that's where these Intents come in. Remember, we talked about the ViewModels, one of its jobs is to interpret user intent and this is what just happened. I'm gonna actually put a little comment here. Mark Intent, okay, or Intents. Let's say Intents, and here, I'm just gonna provide functions that allow these Views to access the outside world. So, in our analogy, you can imagine there's a high-tech door with like a video doorbell intercom system or something and these Views are going to press the intercom button and talk to the outside world and say, please choose this Card, okay? And then the ViewModel which is the door, it can obviously talk to the Model directly and tell it to do things, it's going to make that happen. So these user Intents are kind of things that the Views would say into the intercom, okay? Things that they want to happen in the game. So, the obvious one here is to have a function called choose card, okay? Just like we have in the Model. And this is an Intent that the user might have to choose a Card and this Card right here, we have to make sure it give it its full name. It's a MemoryGame<String>.Card, that's it's full type and all the parts of its type. And this is gonna be really easy to implement, we're just gonna ask the Model to choose that Card. Okay, luckily, our Model happens to have exactly the function that we want. But keep in mind, our Model, again, it might be a SQL database or something and we have to issue a bunch of SQL commands in here to make this kind of the Intent by the user come to fruition. Of course, it's a very simple first app, demonstration app, so luckily, we can easily express this user's Intent in the Card right there, okay? So, this would work. It is nice, we can we have private set so we can see the Cards, we can look at the Model's Cards and we can express our intent to change the world. So we got this door, it's glass, we can see through it, it's protecting us from the outside world but we might even wanna be more restrictive. For example, we might really want that door to be closed and instead of looking through the glass door, you're going to use the video doorbell's video, okay? You know how video doorbell works, people come to the door and you can see them there on a little video screen. So, the analogy here of a little video screen is that we can provide vars and funcs that let people look at this Model in constricted ways. Now, we obviously want people to be able to see the Cards in the Model, so maybe I'll create my own var cards, okay? Which is also an array of MemoryGame<String>.Card, okay? And it's just going to return our Model's Cards. Again, same exact thing here. We have very simple Model, so it's easy to do but the ViewModel might be doing some interpretation here, either to try and massage the Model's data into some form that is more consumable by the View or it might actually be having to do some work, like maybe this data, this Model is coming from over the network and it has to be doing some network requests or something like that. But if we have a choice between adding some complexity to the ViewModel here to massage the data so that the View is simpler, we're always gonna make the trade-off in that direction. We want our Views to be as simple as possible so it's really part of the ViewModel's job to present the Model to the Views in a way that's easily consumable by the Views. Of course, this is a one-liner that returns something so we don't need return right there. So, that's nice. So in this case where we have a fully private door, fully closed door, we have this nice feature where we're letting them on the intercom here and so I might call this one mark access to the Model, okay? So these be functions and vars give the access to the var Models and then this is the Intents. By the way, this // MARK, the reason I'm doing that is if you look up here where it's telling you what's showing in here, it's actually showing you what the exact thing that's shown and those little marks, // MARKs provide these nice little kind of headers for the lists of functions and vars. I kinda prefer this little more closed non-glass door kind of approach to things but occasionally, it makes sense to do that private set and let people have a glass door and see the Model but you're always gonna wanna have this thing where the Intents are called out. This is almost like documentation. It's letting all the Views know, people who are writing View code, here's the things you can do to change the Model. Okay, so, what's this error we have right here? Class EmojiMemoryGame has no initializers. Hm, what does that mean? We have already learned about initializers so what is that? Well, this is essentially in a class, the same kind of complaint that you have a var that's not initialized and indeed, this var, this model var, while it has a type, it has no initial value, right? Needs to be set equal to something here to satisfy this requirement in Swift that all variables are initialized. So, how are we gonna initialize this? We essentially need to do this thing where we do MemoryGame<String>, the type and then in parentheses, we are going to give it its arguments or whatever. It actually has a little one here, we could double click the Cards, it wants the Cards. Why does it want the Cards? Well, if you look at our MemoryGame struct over here, it has an uninitialized var as well. So what's going on over here? It's saying oh, if you wanna create one of these, you're gonna have to give me this, a value for this Card. It's exactly the same thing we had over here when we had CardView isFaceUp was required to satisfy this thing not being initialized. So anytime you have these vars and they're not initialized, then it's up to whoever creates them to initialize them and that's what's going on here. But that's actually kind of bogus here, in this case, because this EmojiMemoryGame, it doesn't really wanna be off creating Cards, doing things like oh, setting them is face up and is matched and all that because it's really up to the MemoryGame to decide which Cards are face up, which are face down. So, really, it's the MemoryGame itself that wants to initialize these Cards right here. So it's almost like it wants to say equals something over here. But it's a little bit of a problem because it doesn't really know, for example, how many Cards are in this game, okay? So where is the number of Cards gonna be communicated from our ViewModel that's trying to create its Model over to the MemoryGame, okay? And the place we really like to do that is here. Instead of having creating a MemoryGame by giving it the Cards, be nice if we just create the MemoryGame by saying create a MemoryGame with this number of pairs of cards, two pairs or five, six pairs whatever of Cards. And then this MemoryGame would say, oh, okay, I will go and create this many Cards, pairs of Cards and I'll set them all up properly and do all that. So, the bottom line here is that we wanna create this MemoryGame<String> thing with some random other argument, not the Cards but some other piece of information. And this is very common to wanna do and the way we do this is with an init, okay? So we go over to here we're gonna add another new function. You don't have to say func init, okay? You can just say init because inits are, by definition, functions and you just give it whatever argument wants. So we want numberOfPairsOfCards and the type, that's an Int, right? And it doesn't have a return value because it's just going to initialize all of our variables, that's what an init does and what's really cool is you can have multiple of these inits, each with different arguments. So, if there were other ways we could think of to create a MemoryGame, we could have other inits, okay? And we've seen this before too. So we go back here, we look at RoundedRectangle, right? When we create a RoundedRectangle and we did open parentheses, look, there were four different ways to create a RoundedRectangle. Its radius or the corner size or some style or something going on here. So these would be four different inits with different arguments, all of them would be used to create a RoundedRectangle. Okay, so that's exactly the same thing going on here. In our case we only have this one init. Now, what is our init need to do here? Well, it needs to initialize all of our vars because we are not allowed to have a MemoryGame without all of its vars initialized. So let's dive right in and do that. I'm gonna start by creating my Cards as an empty Array of Cards, right? My cards are in Array of Cards. I'm gonna make cards to equal to an Array<Card> with open parentheses, close parentheses. In other words, I'm calling its init if it has one with no arguments and when you do that in an Array, it creates an empty Array. So this is cards is now an empty Array of Cards, okay? Which satisfies this requirement of initializing it. But, of course, we need to do more, we need to create this many pairs of Cards, okay? Add that those to this Array. So to do that, we're gonna need a for loop. So this is the first time you're seeing a for loop in Swift. It's for and the iteration variable which is gonna be the pairIndex, right? The index of the pair as I'm gonna do this for each pair. And then you say in. So for in is a for loop. It's the only kind of for loop in Swift and this is an iteratable thing, okay? We saw this before. This is anything that can be iterated, often, it's an Array, okay? In this case, I'm gonna do the same iteratable thing we did over here, which is a range, okay? So a range is an iteratable thing, an Array is also an iteratable thing but here I want a range that goes from zero up to but not including the number of pairs of cards. So this is how you do a for loop, it's going to do it for zero, one, two, up until and not including the number of pairs of cards. So if this is two pairs of cards, then it's gonna be zero, one, and then two is not less than two. So, it will stop, okay? So, inside here, I need to add two Cards. I'm gonna have to say to my Cards Array, append. So append is a function in Array and it lets you add a Card to it. So I'm gonna have to append a Card here of some kind, which we'll have to figure out how to do and then I'm gonna append another Card, okay? one, so just a pair of Cards. so I'm gonna do both pairs. Now, of course, I can't create a Card, one of these little Card things with just open parentheses, close parentheses, that's not legal, it's got these things but if I do the open parenthesis, oh, there, I get this nice bright little initializer here that it builds that lets me initialize every single argument, okay? So I can do that for both of these things. So you get this, if it's a struct, this is a struct, you get this kind of initializer for free. One that initializes every variable. By the way, for a class, it gets a free initializer as well but it initializes none of the variables, okay? So in a class, you either have to initialize them all with equals here, right? Or you have to create your own init that initializes it, like we're doing here, okay? But for struct we don't need an init on this because we get this free one since it's a struct, we get this this free one. So I'm creating a Card here so, of course, I learn to start face down. This is the beginning of the game, presumably, and of course, it's not yet matched and we oh, we got content. Hmm, well, that's gonna be interesting but we know both Cards wanna be like this, okay? So we're getting closer here, right? So we're appending our Card on there but what about this CardContent, okay? That's definitely a problem. It's kind of like I wanna do something like var content equals something here, the content of this pair, that's on this pair of Cards and then put that in here, right? Same content it's gonna be on both Cards because this is a pair of Cards. But it's like I don't really know how to create the content because this content is of type CardContent which for me, is a don't care. Like I don't even know what that is, it could be an Image, Int, String, I don't know so how could I possibly know how to create one of these things? There's just no way to do it. So, who does know how to create the content on this Card? Well, this guy sure does, okay? This is an EmojiMemoryGame. He knows he's creating a MemoryGame with CardContent String, presumably this guy would know how to create the contents of each pair of Cards, right? So somehow, we have to give this guy an opportunity to do this little creation of the content here. We're gonna do that with a function, okay? And I'm just gonna add another argument to my init, I'm gonna call it my cardContentFactory, that's the name of the argument and the type of this argument is a function. A function that takes an Int and returns the CardContent type. Again, this is a don't care, I don't care what it is but I'm gonna give you an Int, which is this pairIndex so I'm actually gonna let you even know which pair I'm making. And you just have to give me a CardContent. That's an Image, give me an Image, that's a string, give me a string, I don't care, this is my don't care for me. So here, I can use this CardContent by calling this function right here, cardContentFactory and I'll call it with the argument the pairIndex. Let's get our capitalization right and this is going to call this function. This type, function type, this could be a String or something like that but it's not, it's a function. So functions are first class types in Swift. They're not even particularly special, okay? You can pass functions around. Again, as you can imagine, in a functional programming language, being able to pass functions around, it's kind of fundamental, okay? It's kind of the basic part of the whole thing. So you don't wanna be afraid of this. In other languages, passing functions around can be torture. Your passing pointers to them and all kinds of crazy things. Here, just literally just explain the types of the arguments and the return and boom, you can pass a function around. Now, this has got a yellow warning here. By the way, we know red warnings are like ah, something won't compile, it's terrible. Yellow warnings will compile but you always wanna fix these because they're often gonna lead to future problems if not an immediate problem. So what's this one's saying? It's saying the variable content, that's this variable right here, was never mutated. Mutated, that means changed, right? It's never mutated. Consider changing to a let constant. So it's basically saying don't call this a var if it's not variable, okay? If it doesn't vary, it shouldn't be a var, instead, in Swift, we use the keyword let and we could just type it here, let to fix it or we could also use this warning right here, click on it and do the fix. So the fix is gonna replace var with let right here, watch. Fix. Okay, so it makes a let. And let is a really nice word here because this reads very much like English. That content equal the results of calling the cardContentFactory for this pairIndex, okay? Every time you have any kind of variable that doesn't actually vary, in other words, it's a constant, you always wanna use let here. Now, another thing to note here is kind of interesting is we didn't put a type on this thing. We didn't say type CardContent even though that is what type this is. This is of type CardContent because that's what contentCardFactory returns but we didn't have to do this. And this is part of Swift just figuring things out for you, inferring the types whenever it can and we're really gonna see that on the other side of calling this init in just a moment right here, okay? All right, so let's go back to that side and see where we create our MemoryGame. And put this on a separate line so we get a little more room. So now, we've added a second argument here to number of pairs of cards it's this cardContentFactory, and this value that goes here has to be a function that takes an Int, which is the pairIndex, and returns a CardContent which we know to be String here. So it has to return a String. So let's create such a function. Watch this, func, I'm gonna call it createCardContent. We know that it has to take an Int which is that pairIndex, going Int, and we know it has to return CardContent, which would want to be a String. Okay, because this is a MemoryGame of String, all right? Now, I'm gonna return, let's just return for now some emoji, how about that? Put the same emoji on every single Card, maybe a smiley face right there. So here we are returning a string right there. And we can now use this and say createCardContent. So this is an argument that takes a function that takes an Int and returns a String. This certainly qualifies and so this is legal. Look, no errors, no warnings. This is all perfectly legal way to do this. However, we would never do it this way because we don't want to have to go be creating these extra little functions to do that. Instead, we would inline this right here inline with this code. So watch carefully now I'm gonna go through the process of how we take this function right here and inline it right here, okay? This inlining of functions in Swift is called a closure and it's called a closure because it actually captures the information from the surroundings that it needs to work. We'll talk about that later but basically, you can think of it for now as an inline function. So we're going to select this function, everything about it except for its name. When we inline, it doesn't need a name, okay? No need for a name because just sitting right there inline. So I'm gonna select everything except the name and I'm going to cut then I'm gonna go here and paste it here instead of the name here. So, paste. Now, this almost works as is but there's one thing I always have to do when I do this is to take this curly brace right here, cut it, replace it with the word in and then paste the curly brace over here at the start. And essentially, the curly braces have to surround the entire inline function. So, that's why we move the curly brace out in front of its arguments and return type there and use this in to separate it. Okay, now, we don't need this func up here and you can see again, no errors, this was perfectly legal way to inline this function. By the way, you probably recognize this in, we used to somewhere else over here in our View with for each, it used in and had a little argument here. So, this is gonna start making a lot more sense to you once we finish up with this. And what do I mean by finish off with this? Isn't this just finished? Well, not quite because just like when we were over here, Swift was able to infer that this was type CardContent so we didn't have to say colon CardContent here, right? So that kind of inferring that we call that type inference is really nice in a language where everything has to be strongly typed, every single var has to have a type, okay? That's somewhat of a burden but type inference helps make it so that it's not such a burden. And what kind of types can Swift infer in here? Well, a whole lot of them. It knows the type of this var, which is a function that takes an Int and returns a CardContent. So that means that we don't need to say this is an Int and we don't need to say this returns to String, okay? Swift can infer that. Again, look, no errors, no warnings, it's perfectly legal. You don't even really need these parentheses right here, okay? 'Cause they're not really doing anything at that point and here, this pairIndex in, again, like an awful lot like index in over here 'cause this, it turns out is a function as well, kind of a special function because you can list the Views there but it's the same syntax that's going on here. But we're not done yet because, of course, we know this is now a one line function that returns this string. So we don't need return right there. And we could even clean up some of this space here, remove some of the space like that and even more, we know that if you have a curly brace thing, that is the last argument, right? The last argument that this init has two arguments and this is the last one. We can do the same thing we did with the last argument for ForEach, the last argument to HStack, the last argument to ZStack, do the exact same thing here which is to get rid of the keyword, okay? And put the curly brace thing outside, floating outside so we end up with this very streamlined function here and even more than that, notice that since we're just always returning a smiley face, you don't really even need this pairIndex here but you can't delete it, you have to mark it with an underbar just to say yeah, I know this is supposed to take an argument but I don't need it so I'm just gonna use under var and then in Swift, you're gonna see we use underbar anytime we mean, it doesn't matter what this is, I'm not gonna use this things, kind of unused things so we're not using that pairIndex and so it does this. So, it's really nice simple syntax here. And you're gonna wanna get used to this because we're gonna be doing this calling functions. You've already seen in the View, we do these things all over the rest, these curly braces, they're everywhere and this is functional programming so we're gonna be passing a lot of functions as arguments to other functions. What if we wanna do something where we're actually returning a different emoji for each pair of cards, okay? We don't wanna have every pair of Cards have the smiley face, that would make the game very easy but we don't wanna do that. So how would we do a more complicated thing there? Well, to do that, first of all, I'm gonna, instead of doing this, setting this right in line here, I'm gonna try and create a function here, I'm gonna call this createMemoryGame and then I'm gonna put this into a func called createMemoryGame. It's gonna return in MemoryGame of string, okay? And this and it's going to essentially return this. And here, we're gonna do something more complicated than just that. Remember, this is the pairIndex in. And so, how am I gonna implement this? I'm going to create a little emojis Array. This is gonna be an Array of String, an Array of emojis, actually. I'm gonna set it equal to a constant Array. So this is what this the syntax is for a constant Array. So you just do open square bracket and then the things you want in the Array and then close square bracket. So these things for me are gonna be emoji so I'm gonna go over here, let's go back to our Halloween theme here and get Mr. Ghost, there's a ghost and then in this one, we'll put some other Halloween thing. How about pumpkin? Maybe a pump, no, I'll go pumpkin. Okay, there's a pumpkin. And we have this. So this creates an Array of String because these are Strings and in here, return this MemoryGame, so my little Card factory is just going to return emojis sub pairIndex. So this is how you access an Array. You just put square brackets around whatever the index is and so this pairIndex, it's gonna be zero then it's gonna be one and so we're gonna get index zero then we're gonna get index one. So the first pair of cards will be a pair of ghosts, second pair of cards are gonna be a pair of pumpkins. So, this code, no errors but oh, we have an error up here. What does this say? Cannot use instance member createMemoryGame within a property initializer. Property initializers run before self is available. Okay, so what does that mean? Well, here, I've told you that we cannot, in Swift have, any variable that's not initialize to something, what's even more restrictive than that, we cannot use any functions on our class or struct until all of these are initialized. So that major catch-22 here, I wanna use a function on my instance right here to create this MemoryGame but I can't until this is initialized. So, it's like wah! So, how are we gonna fix this? We're gonna turn this createMemoryGame here actually into a static func. So, a static func, that makes this a function on the type, all right? So instead of being a function that you send to an instance of EmojiMemoryGame, hopefully, everyone knows in the object-oriented sense what an instance means, we're sending it to the type. And the syntax for that is we type the name of the type in, MemoryGame. and the function and that only works for static functions. So this is a function on the type, not a function on a MemoryGame instance, EmojiMemoryGame instance, but actually, on the type itself. We have actually already used this. Over here in ContentView, Color.orange, Font.largeTitle. These are types, Font and Color are types so these are, in this case vars but you can do with functions or vars, vars on that type. These are static. In fact, let's go and look in the documentation and see this happening. So, how can we jump into the documentation from our code? Here's a really cool feature. Hold down the Option key. When you do, when you mouse over something, it will have a question mark on it. So I'm gonna click on Font and it gives me a short description of what font is but it also has this nice a little link right here. Boom, take me to the documentation and show me Font. So this is how you can get into the documentation. Of course, you can also go Window, Developer Documentation up here but doing that Option + click, it's usually how we get into the documentation, actually. And if you look in Font right here, you can see, there's largeTitle right off the bat and see, it's static. It's also a let, so largeTitle is a constant. Static let so it's on the Font, on the actual type itself, the Font type. You can see there's all these other ones as well. You probably wanna use one of these also for your homework. You can kinda experiment what they look at. Like, these are just built-in fonts and these are fonts we'd like to use because everyone else is using them and all the other apps and so as users use your app and then there's another app, they see the same types of fonts and these fonts just have a little different style or whatever but it's same throughout the entire system, okay? While we're looking here in the documentation, let's show you some features of the documentation viewer here. You can search, obviously, up here. So let's, for example, search for Array. And if I search for Array, here's all the matches where it finds Array. The first suggested one is a likely going to be a class or struct with that name. Here is array, you can see Array of Element, right? It's a generic type. This is it's don't care. it's called Element, just like our don't care is called CardContent. And these descriptions are really detailed, tell you all about an Array, how it works. You're definitely gonna wanna read this for Array, familiarize yourself of what Array can do. That will definitely help you with your homework. If you scroll down here and look through all these, I don't expect you to figure out how all these work but you definitely wanna be able to search through these here and see if you can find a particular function that might help you with your homework, okay? And same thing with View, let's go look at View. Okay, View right here. Another description of View up here, we're gonna learn all about that, we already have learned quite a bit about View here. Now, if you have so many functions and vars on it that it's divided them into sections right here. A couple of interesting ones to look at might be layout, which we're gonna learn a lot more about next week. That's where you find things like padding and also, rendering, okay? So, rendering, it's we can learn how to scale and rotate and blur things, other kind of stuff. So, again, this is something you're probably going to find stuff in here that will help you with your homework. So, part of the homework is really to just start kind of maneuvering around in here. Some of this stuff you're not gonna understand at all like State. You're gonna be like, whoa what the heck is that? I don't expect you to be learning any of this by reading the documentation but I definitely expect you to know about the documentation, know what's in there so you can go search around and find things, okay? All right, so back to here. So we did this a nice static function. Again, no errors, no warnings. We're using this as kind of a utility function to create our MemoryGame. Now, that we have a ViewModel, okay? That's looking at our Model, all right? Our Model, we don't actually have our Model play the game when you choose but hopefully, at least, say card chosen. Let's go back to our View and use our ViewModel, all right? Remember that our View always wants to use the ViewModel to access what's in the Model. Also don't forget that the View is its primary thing in the world is to reflect the current state of the Model, okay? So, whatever's in the Model, it wants to always show. So let's start down here with our CardView. Currently, our CardView has this one var isFaceUp but really, it should be getting that isFaceUp from the Card that it's viewing. So I'm gonna change this var from isFaceUp to be a Card, which is a MemoryGame<String>.Card. By the way, you notice we've been typing this out a lot. This is a lot to type. Of course, Swift has a way to make it so we don't have to take that so much and we'll talk about that next week. And then here, instead of saying isFaceUp, I'm gonna say card.isFaceUp. And instead of always showing a ghost, I'm gonna get the Card's content. Now, this is pretty cool right here. Content in our MemoryGame, this is a type CardContent, don't care, don't even know what it is but of course, in a EmojiMemoryGame, we make it be a String. And so, that's why over here in our content View, this is a MemoryGame of Strings card so the card's content is a type String and that's what a Text wanted, it wants a text String all right? So up here, our CardView, it's not going to take in isFaceUp anymore, it wants to take some sort of Card. So we're gonna have to find some way to provide it with a Card. Now, how are we going to provide that thing with a Card? Well, we're gonna get those Cards through our ViewModel. So, we need some sort of var here, which I'm gonna call viewModel Again, you would not call a var viewModel just like you wouldn't call a var model but I just wanted, in this code, for you to see when I'm accessing the Model, same thing here, I want you to see when I'm accessing the ViewModel. So what type is the viewModel? It's an EmojiMemoryGame, okay? This is our ViewModel type, right? Class EmojiMemoryGame right there. So, since this is a class, this is a pointer to it. if I had other Views that were accessed in the ViewModel, they'd have pointers to it so there would only be one EmojiMemoryGame somewhere. So where are we going to create this EmojiMemoryGame? Well we're gonna create it in wherever this content View is being created. We're gonna do the same thing we were doing before with the Card's isFaceUp, it's actually the same thing we're doing here. And so where is this created? So, this is a time to dive in a little bit to this boilerplate. Remember, I told you there was this AppDelegate, SceneDelegate boiler plate. Well, if we click on the SceneDelegate, we'll see there's some junk in here which we're gonna talk about later but here is the very important line of code that creates the ContentView that is used as the Windows' main View, okay? So this ContentView is already complaining missing argument for viewModel right? It knows that over here, we've got this viewModel It doesn't set equal to anything so we've got to do that when we create it. So I need to say viewModel is something here. So I'm going to have this be a var called the game and I'm gonna say let game equal EmojiMemoryGame, open parentheses, close parentheses, I get a free init because this is a class, right? A free init that initializes nothing but luckily, I initialize my only var here using this, right? So this is gonna work. So here we go, we got this game for passing the ViewModel. And by the way, if we go back here and look at our ContentView, it's still gonna be complaining down here. This is that glue that glues this to the gray area. It's creating a ContentView to show in this canvas over here. Okay, so, this ContentView also needs a ViewModel here. For this, I'll just create an EmojiMemoryGame on the fly because this is essentially for testing or whatever so it can create this thing on the fly, doesn't need to put it in a variable and all that. Okay, so, we're getting close. Now, we have our ViewModel right here. How do we use the ViewModel to get the Cards that we're gonna show? Well, right now, we just throw it show four Cards, zero, one, two, or three. We're using a range right here. Gonna click this again. We're using this range and I told you this could be any iteratable thing. So how about if we just make this be our ViewModel's Cards, okay? This is an Array of MemoryGame<String>.Cards and so, this should just work, right? But it doesn't work, okay? In fact, I typed an Array thing here and it says cannot convert from value to expected argument range of Int. It still thinks I want range of Int in here. That's because kinda mislead you a little when I said this could be any iteratable thing. It actually is any iteratable thing where the things that's iterating over are what is called Identifiable, okay? These things have to be Identifiable. If they're not a range of Int, they have to be Identifiable. So, why? Why do these have to be Identifiable? Well, for example, let's say you're want to do animation and let's say these Cards are moving around, okay? Moving into a different order or something like that. This for each needs to be able to identify which Card is which so that the View it creates for each Card, which is what this is, this CardView, it can keep that View in sync with these Cards. So these things have to be identifiable and right now, if you look at this, this is an Array of Card, right? ViewModel.cards is this Array of MemoryGame Cards. If we look at MemoryGame Cards, they are not identifiable, there's no way to identify them. In fact, right now, they're all the same. Two Cards that match would be the same because they have the same content, they might be the same isFaceUp. There's no way of identifying them. So, Swift has a formalism, a formal mechanism for identifying something, making something identifiable and it does it with something I like to call constrains and gains. So that's when you require a struct to do a certain thing, you constrain it to do a certain thing but if it does, then it gains certain capabilities. Now, we're gonna talk all about how constrains and gains works next week. And we've already used constrains and gains, actually, here, colon View was constrains and gains. We constrained ourselves to have to do this body, okay? But we gained all the stuff that View does, okay? So, that's constraints and gains in the struct. We're gonna do the same thing with this constrains and gains with this struct. We're gonna say constrains and gains Identifiable. Identifiable like View is what's called a protocol and that's the heart of this constrains and gains business. And again, we'll talk about protocols a lot next week. Unfortunately, you don't gain much with it except where you gain the ability to be identified but mostly, you are constrained and the constraint of Identifiable is that you have to have a var called id Now, luckily, it can be any type you want. I'm gonna make my id be an Int but it could be a String or anything you need to do to make this thing identifiable. Of course, as soon as I add another var here, now, my Card isFaceUp blah, blah, blah is not doing all the vars. So for both of these, I need to add an ID. And what I'm gonna use for my ID is my pairIndex times two and for this guy's ID because I want this Card to be obviously, to have its own identifier, I'm gonna do pairIndex times two plus one, okay? So now these things have unique identifiers. Now this is fully identifiable, that's all we need to do, we just have to make sure that these Cards are uniquely identifiable, again, so that they move around, we can animate them or whatever. By the way, it's a little annoying here that I have to say this every time for a new Card. I could just put that down here, by the way. You're absolutely allowed to have some of your vars be initialized that way and then we don't need to have them here, okay? That cleans up our code a little bit there. Okay, so now that this is Identifiable, when we go back to our View over here, it says, oh, that's fine, okay, you've got an iteratable thing of Identifiables all as well. Of course, this is no longer the index in the range, okay? This argument is the Card that's in the Array, okay? And of course, you know that this is an inline function and this is the argument to it and it's just ForEaching through these cards in this Array and so this is each Card and that's what we're gonna pass right here. And that's it. So this is how we attach our Model to our View through our ViewModel, okay? So our ViewModel provided essentially a window or a portal on to our Model through this Cards Array and through choose card which we haven't used yet but we're going to. For us to see our Model over here and our View is just always going to reflect that. Now, we're gonna run out of time today to show you how it does the auto-reflection, we're gonna do that first thing next week's lecture. But that's a key part of making all his work but at least we're gonna be able to see here that this View is always drawing whatever is in the Model, okay? Get it through the ViewModel but whatever it gets, it's always drawing that here. And we're gonna see that in action. Let's go ahead and we'll run our app. And here it is. And we've got four face-down cards. Why do we have four face-down cards? Well, because here, when we created our MemoryGame, we said we wanted two pairs of Cards so that exactly why we got four Cards, it's two pairs and over here in MemoryGame, we have all our Cards start facedown. So let's change this in our Model to be true, okay? All Cards are now created in our Model faceUp true. Let's see if that affects our View. Whoa, it did and even, look, there's the two kinds of things that we put on there. See that? Okay? Now, we could, let's say put another one in here. Over here. What's a good Halloween thing? How about spider? Yeah, spider. Maybe a spider, by the way, we have two Cards here. We could say three now that we have three but another cool thing to say is emojis.count, right? Because emojis is this Array. Of course, I don't need this, by the way, can infer that as well. So, this emojis.count, this is just a var in emojis, in Array, rather, that is going to tell you how many things are in here. We run again. And we got it. Okay, so, our View is very nicely always reflecting whatever it sees in our Model. That's great. What about this ability to express an Intent? Like I wanna choose a card, okay? I wanna be able to go here and tap on a card and have it be chosen for the purposes of the game. So, that's also very easy to do because we have this ViewModel. For every Card, I'm just gonna add a little thing on it called onTapGesture. So, onTapGesture is a function that takes another function as an argument. The function it takes takes no arguments and it returns nothing, okay? So, this is the function we're gonna put. So, here is a function that takes no arguments and returns nothing right here. And what I wanna do in here is ask my ViewModel to do something, an Intent, which is to choose this Card, okay? That's this Card right here, I'm going to choose this Card. So onTapGesture perform is just something in View, all Views know how to do this onTapGesture perform. This is the only argument and thus, the last argument so we don't need that on here. A lot of times, if we have something like this, we're going to make it a little more readable by putting this little embedded function here on its own line like this. And you can also see that we have an error here. Now, this is a very interesting error because probably, in about a month and a half, this error will no longer appear, okay? This has been fixed or changed, however you wanna describe it, in SwiftUI. Swift, all changes to Swift go through a public review process and this has gone through this and been approved so it will happen. And essentially, what's going on here is sometimes, when you have these inline functions, you need to put self. in front so that Swift knows exactly what's going on, okay? And I'm not gonna explain in detail today because we're out of time. What I mean by that, will explain it next week or the week after. But that's what this error is saying right here is to put the self. Now, I recommend any time you see this error, any time you see the words requires explicit self to make capture semantics explicit, insert self, just choose to fix it, okay? And then that'll put self in front. I mean you could put self in front of any var, okay? At any time. It never hurts to put self in front. Some people have kind of taken a strategy of I'm just gonna put self. in front of every single var and then I'll never have this problem where I have to do that fix it. Given that in a couple months, this self. is not actually going to be required here anymore. I'm not sure what the right strategy there is but in this class, if you see that explicit semantics insert self thing, just do the fix it and put self in front and all of you well there. Okay, so let's see if this works. Okay, when we click on these Cards and this tap gesture happens, we should get choose card appearing on our console. So let's try it, click. Oh, look, there's something down there. there it is. This is our debugger over here. This is our console. We can actually use these buttons to hide the debugger so we're only seeing the console, get this back over here. And hopefully, when we click on different cards, yeah, we're getting different things. So this output card chosen is coming from this line of code right here. This is in our Model. So our View was able to invoke code in our Model by simply executing the Intent function in our ViewModel. So that's how communication between the View and the Model happens when some gesture happens in the View. You can see that it's basically turning our Card into a String by telling us everything about the card, okay? All the value of all of its vars. I told you this was a cool feature and it is really good for debugging, okay? All right, so we're out of time for this week's lecture. Your homework is to clean this up a little bit. For example, notice these cards are not shuffled so it's really easy to play this game, right? 'Cause the cards are all next to each other so you're gonna shuffle these cards, you're gonna make them stop being tall and skinny like this and you're also going to put a random number of cards in here from like two pairs up to, I think, I did five or six pairs, whatever says in the homework assignment. So every time, it's gonna have a different number of cards and they'll all be shuffled. And that's your homework. So you can see that most your homework is just to reproduce what I've done here to get it to this point. The changes that I mentioned, most of them are one line of code so it shouldn't be a lot of work for you there but it's one line of code but it means understanding what's going on here. Okay, that's it for this week. Enjoy your homework if you have any questions about it, you know to go to Piazza and we'll be right there to answer them for you. - [Announcer] For more, please visit us at stanford.edu.
Info
Channel: Stanford
Views: 119,588
Rating: 4.9608016 out of 5
Keywords: Swift, SwiftUI, Xcode, iOS, iPhone, iPad, Stanford, CS193p, coding, iOS programming, MVVM, ViewModel, struct, generics, closures, type system, declarative user-interface, Memorize, emoji
Id: 4GjXq2Sr55Q
Channel Id: undefined
Length: 103min 52sec (6232 seconds)
Published: Mon May 18 2020
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.