(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.