- [Narrator] Stanford University. - [Instructor] We are
back for lecture four. Stanford CS193p, spring of 2021. We're going to start
right off with the demo and complete what we started
with this MVVM architecture. We've done the Model,
we've done the ViewModel. Now we need to hook all
that up to our View. After we get that working,
I'm gonna come back to the slides briefly to
talk about two things. The first is another type called enum and then this special version
of enum called Optional. Probably one of the most important enums, if not the most important
enum in all of Swift. Then we'll go back to the demo
briefly to finally make it so that our game actually
plays the memory matching. But first, we need to finish
up our MVVM architecture. So let's get back to
that part of the demo. In lecture three, we built our Model and our ViewModel for our Memorize game. Next step we're gonna
do is hook these things up to our View. Our View currently is
essentially demoware. It's showing emojis directly. I'm gonna get rid of all of this demo. including this @State isFaceUp which we use to temporarily
flip the card over using this tap gesture. All of that now needs
to happen in the Model. It's in the Model that we're keeping track of whether cards are
face up, not in @State in our CardView over here. And we're not going to be ForEach'ing over the emojis anymore. We're gonna ForEach over the cards. Recall from the lecture
that one definition of var body is "give me a UI that shows me what's in the Model". Our ContentView here needs
to be able to see the Model so it can give out a UI
that draws the Model. We're gonna do that by
adding a var viewModel which is of type EmojiMemoryGame. Now you're always gonna
have a var like this at the top levels. When you're showing what's in the Model. Of course, you have to be
able to see your ViewModel. Probably not gonna call it viewModel. Likely this would be
called something like game. I'm gonna leave it called viewModel so that as I use it in here,
it will be clear to you, oh, we're accessing the ViewModel there. But we can't just add a var like this to our ContentView,
without setting its value. We could do an = here but we generally don't do that. If we think of a View as just an agent for
showing what in the Model through the ViewModel, then more likely, we're gonna wanna pass
it to it as an argument. So I'm not gonna set it
equal to something here. And instead, everywhere
we create a ContentView, I'm going to pass it in a ViewModel. Where do we do that? Well, the main place we
do that is in our app, the main program. Remember this, here's where
we create the ContentView. It's going to show our entire UI. I'm just going to create a little var here let game = EmojiMemoryGame(). Now this is an interesting
line of code here. I'm creating an EmojiMemoryGame by just doing open
parentheses, close parentheses. That's because of the free
init that classes get. structs get a free init
that's even better. It initializes all the vars. But classes only get a free
init that does nothing. That's okay though because the only var we
have, we initialize it here. So that works. If that wasn't the case, we'd have to make our
own init here in a class. So we're using that free init
to create our emoji game. The other thing, notice I made it a let. You might be a little concerned about this because clearly, this
emoji game is gonna vary. We're going to be flipping cards over and doing matches and all kinds of stuff. So how can I make this
game be a let, a constant? Well, remember that this is a
class, it's a reference type. This game is a pointer to it. And when I say let game, I just mean I'm not gonna change this
pointer, but what it points to, I'm definitely gonna change that. Classes, like our ViewModel are mutable, can always be changed through the pointer. That's what wonderful about them. Imagine what's terrible about
them because it's dangerous. Anybody who has appointed
to it can change it. But it works nicely here 'cause we want our ViewModels
possibly to be shared amongst a lot of different Views, not in our current app, it's simple, but we might down the road. So I created a game here. I'm just gonna pass it as our ViewModel to our ContentView. That's going to set
this variable right here but there's actually another
place we create ContentViews you don't wanna forget about that. Down at the bottom of
this file, our Previews. We even have two Previews,
light and dark mode. So we wanna do the same thing here. I'm gonna create a game
and I'm gonna pass it along as the ViewModel, pass the
same ViewModel to both of them. Keep in mind, this is a pointer, these are shared, no
reason two ContentViews can't be looking at the same ViewModel. And so now we're totally hooked up here. Our ContentView has been hooked up to some EmojiMemoryGame to show. And now, we just need to make our var body do its job and show it. How's it gonna do that? Well, let's start with the cards. Right here, we wanna ForEach
over to all the cards. So I'm gonna say
ForEach(viewModel.cards, .... We used to have emoji here but now, we're going to ForEach
over the cards themselves and we'll have to create
a CardView based on that. This is ... ... this in our ViewModel Just a var cards returns an
Array<MemoryGame<String>>. And it's easy to implement. It's just returning the Models cards. And yes, we don't need to return there. And the Model's cards is
this a private(set) var. It's a private(set). We can get the cards in our ViewModel. We can't set them directly but we can get them and return them through our own var to our View. Remember, we could have
done private(set) right here and then not had this cards at all. And if we did it this way,
and instead of having this, then in our View, we would be
saying viewModel.model.cards. Whether you do it this way where you're exposing the Model directly and letting people look
at the cards in the View, or whether you do it this
way that we originally did it over here, kind of
an "art of programming". Depends a little bit on
what kind of Model this is, whether it's just a data store type Model or whether it's got quite a bit of logic. I tend to be a fan, at least at the start, of making my Model completely private and exposing it via vars
and funcs to my View. But if over the long-term, I decide that this is just unnecessary overhead I might go back to
doing private(set) here. We're making great progress here. When we are ForEach'ing
through the cards, of course, this is no longer "emoji
in", this is "card in" because we're going through
an Array of Cards here. And our CardView is no longer being passed the emoji as content. Instead, our CardView is
essentially now redefined as a View that shows
what a card looks like. So I'm gonna add a var
to this called card, which is a MemoryGame<String>.Card. And this CardView takes a Card and just builds a body for it. This is also a var body
that is just showing what's in the Model. Notice I haven't passed
the entire Model to it. This is not passing the ViewModel to it so it can access the entire Model. I'm just passing a tiny slice, tiny little sliver of the Model. One card, and this is highly recommended. When you build a View, only pass into it the minimum it needs
to do what its job is. CardView's job is build
me a UI that shows a card in the Model. Only pass it the card. Also, this does not want to be a var. These are read only structs. This card cannot change anyway. So don't make it a var. The only time you're gonna have vars is if you're using @State. And we said, @State very rare in a View. So you're not gonna have a
lot of vars in your Views. How about this ViewModel? We'll make it a let now as well, although that is going to change as you'll see in the future. So we're ForEach'ing through the cards. Each time we got a card,
we're gonna say card: card. Here's a case where you might be saying, hmm, luxury redundant to me. I really wanna do this underbar thing so that I'm not having this external name be card: card, this is obviously a card. And we could do that. The way we would do that,
get rid of this card because that's coming from here is to add an init to our View. Views can have an init,
they're just structs. Then we could have an init
where the first argument is underbarred so that you
don't have to specify it. But it's a trade-off here
between the complexity of adding more code down
here and making this look slightly better maybe by not
having the word card here. I prefer not to add the init here. Now that we're passing a
card into our CardView, things like isFaceUp and
content come out of that card. Viola, we have a CardView, no errors. It's displaying this card,
building a UI for this card. And we have our ContentView which is using the ViewModel
to go through the cards. We're down to one error left. Look at that one error. It's this generic struct ForEach requires that MemoryGame<String>.Card
conform to Hashable. Holy cow, what does that mean? We know that conform to is "behaves like". So why is it saying that? Well, we left in this little id: \.self. We were using self in our emoji strings as our unique identifier. Remember that everything
in the cards Array has to be uniquely Identifiable. So we were using self
when they were strings. We certainly do not want that here. Our Cards want to be
Identifiable on their own. Now I expected when I removed
that to get a nice error here that said card must
behave like Identifiable, which we actually got when we originally put strings in there. But sometimes the Swift
compiler is unable to figure out a complex expression. And in this case, for
some reason, it cannot and it even says failed
to produce diagnostic. Please submit a bug report. So it wants to use submit it. But luckily in this case and this can be frustrating
when you get this 'cause you're like, I can't
see what the problem is. What's the problem? Help me compiler, and
it's not helping you. But luckily, in this case, I know that the problem
is that these Cards aren't Identifiable and they need to be be 'cause anytime you do ForEach
over an Array of something, it needs to be Identifiable. How do we make our Cards Identifiable? Well, let's go back to
where our Cards are defined, right here, this struct and Identifiable is just a behavior just like we have our
ContentView behaves like a View. We're gonna make our cards
behave like an Identifiable. What the heck is an Identifiable? Super simple. It is just a single var that
is used to identify this struct against all other Card structs Now we know that when
we do "behaves like", that it's a double-edged sword. One, we get this nice ability
to identify ourselves, but two, we might have
some responsibilities. And with Identifiable, you definitely do. With View, our responsibility
to behave like a View as we had to do our body. With Identifiable, we're gonna find out
what the requirement is by going here to the
error and clicking fix. Fix is saying, do you want
to add protocol stubs? Okay, we're gonna learn about
protocol stubs next week. But protocol stub basically is saying, what are the vars and functions
necessary for me to say I behave like an Identifiable. So let's say, fix and see. Oh, it added this one var right here. var id, its type is ObjectIdentifier. This is really a "don't care". And if we go look at the
definition of Identifiable, we're gonna find out that
this can be any type we want since it's a don't care,
although it is restricted to having to be Hashable and Equatable. And that just means we can
look it up in a hash table. We want things that are
Identifiable to be able to look up in hash tables. So I could pick anything. This could be a String,
those are certainly Hashable. It could be an Int that's
definitely Hashable. For me, I want an Int because I have a great idea
how to make my cards unique. And I'm gonna show you in a minute. And viola, my card is Identifiable. No errors. I'm gonna put this id down at the bottom. It's not really as important
as all my other vars. That's why I want it at the end. And we're almost there. We do have an error here though. Missing argument for
parameter "id" in call. Oh yeah, because I didn't
set this equal to anything. So now when I create a Card, I have to both specify its content and I have to specify the id. Well, here's my great idea. I'm gonna have the unique identifier be the pairIndex * 2. I'm gonna have the id of
this second card in the pair be pairIndex * 2 + 1. Now, all my cards have
a unique identifier. As I go through the pairIndex's
here creating all my cards they're each gonna get a unique id. Now we can identify a
Card from any other Card. Now that these Cards are Identifiable, if we go back here, no more
error, build succeeded, run. And there's our UI. And indeed, it's exactly reflecting what's in the Model through the ViewModel. We've got one, two, three,
four pairs of cards. Go to our ViewModel. We see how many pairs of cards or and we're also seeing that
the cards are all face up. Why are all the cards face up? 'Cause in our Model, we have
isFaceUp default to true. If we change isFaceUp to
default to false and run, now all the cards are face down. And if we went back to our Model and said, oh, give me only three pairs of cards instead of four and run, we get three pairs of card. Clearly our ContentView is giving us a UI that reflects what in the Model. Now you might think the
fact that I was going back to the simulator all the time
means that once we hook up our Model and ViewModel, we can only use this simulator now. No, we can indeed go over here and bring back our Preview. And we can resume and see it here. Make it smaller. And again, we can change things. Go back here to our MemoryGame. Let's change this back to four. Oh, I lost my Preview. I guess if I go back
here, oh, there it is. And we'll resume. And there's four cards, but
this is a little bit annoying that every time I go back to my ViewModel or to my
Model, I lose my Preview. So let me show you a trick for that. We're gonna go back to our
View here and click this pin. This little pin says,
keep this guy on screen as I go to my ViewModel and my Model. So that if I change, for
example, the cards back to isFaceUp = true and then resume, the cards are all face up. We're back to my ViewModel. Again, I'll make this back
to three pairs of cars. Instantaneously shows me the cards. This is fantastic. We have got this nice UI. I'm gonna fix one other thing in my UI that's bothering me by the
way, which is this VStack. This VStack was used be
holding those buttons along the bottom. Don't need that. Let's get rid of this VStack,
make our UI even cleaner. What about flipping the cards over? Tapping on the cards to flip them over. I'll put this in live
Preview mode here and resume. Nope, no flipping over the cards. It doesn't work in our simulator either. Tap, tap, tap, nothing. Why is this not happening? Well, because our Model is responsible for flipping these cards over, that's part of playing the game. That flipping needs to happen
when you choose a card. So we're gonna fix choose
to start out anyway by flipping the cards over. Eventually, it's gonna play
the entire game in here but first, we wanna flip it over but we have to hook up this
choose to our UI somehow. We took away that onTapGesture down here that was just diddling with @State. So we needed to put it
onTapGesture back somewhere here and have it hookup to the Model. Let me do that right here. CardView, we can say onTapGesture. And inside here, I want to express the user's
Intent to choose a card. This is where those Intents come in. And Intents are all
recorded in the ViewModel. I even put a comment (gonna to use this // MARK:
comment which I'll show you) where I'll put my Intent and I might have multiple of them here. This is where I'm gonna put the functions that let me express user Intent. This // MARK: - is so
that when you go up here, lets you go right to it. Just put a little header right here. So there's only one user
Intent that our game has. And that is the Intent to choose a card. And our cards are
MemoryGame<String>.Cards. That's their type. This Intent is something
that we gotta hook the UI directly up to. Our UI is gonna express
this Intent directly by asking the ViewModel
to choose this card. Card that's in our ForEach
the card that we're drawing. We're gonna choose it. This is what the code would look like generally in your View, asking ViewModel to express this user's Intent. Implementing this is trivial
in our simple little game because our Model lets us
choose a card directly. This is a one-liner. Not all Models are so
accommodating to their ViewModels. This might be quite a complicated series of instructions to modify a database or go out in the network and do something. Whatever, it all depends on
how complicated our Model is. In our simple case, we
can just do one line, call our Model's choose
and get the job done. Let's check and see if
we've successfully wired up the user's Intent through our ViewModel and it's making it to our Model by adding a print statement. print is a great way of debugging. We haven't even talked about the debugger. We're actually not gonna
talk about it too much but print statements are
super way to just print out what the state of your
application is at any time. They only work with the simulator, work with the Preview here. So I'm gonna run the simulator and I'm gonna tap on a card and I'm gonna hope that
it prints hello somewhere. Uh-oh, it's not doing it. Well, it actually is doing it. It's doing it down here in the console. This console you have to drag up. And again, oh no, I
don't see hello in here. Well, this is actually a shared
space between the debugger which is what you see here and the console over here. We can divide the space up and use these buttons to
hide one or the other. So don't be fooled about, oh, I put print statements
in, they're not printing. This is where they are gonna show up. There's even better way to deal with this. If this is down into like, oh,
I printed what's happening. You go up to the
Preferences menu in Xcode. Whole lot of preferences in Xcode. I'm going to go to this Behaviors one here and this Generates output. This is essentially saying,
what is the behavior of Xcode when the simulator generates output? I'm gonna go in here and
pick show the debugger with the console. It could show both of the
variables and console. That's the debugger and the console but I'm gonna do just the console. So now if I'm running my program and this thing is down and it generates some
output, which it did already even before I clicked a
card, it brings this up. So you're almost certainly
gonna wanna set that. Otherwise, you forget, the
prints will be printing and you won't be able to see it. Whatever do I click on a helicopter? Hello, hello, hello, hello. So it is working. Our UI has been hooked up. The user's Intent is being
reflected here in the Model. But we want something more
than just hello to happen here when we choose, we want
to flip this card over. Now you might think
this is a simple matter of just saying card.isFaceUp equals not card.isFaceUp. By the way, doing this, taking a Bool and making
it equal to not the Bool there's actually a function
in Bool called toggle which should do exactly that. Toggle, toggles this Bool. When I do this, oh no. Cannot use mutating
member on immutable value. card is a let. It's saying I can't
call toggle on this Bool because card is a let and
indeed, this card is this card. And all arguments to functions are lets. They are constants. Which makes sense in a
way, passing a card in, especially passing
structs that are copied. You want them to be lets. So where is the card we
actually wanna change? It's in here. This is the Array of our Cards. This is the Cards we need to change. So we've gotta somehow find
this Card in this Array and then change the Card in
this Array to have it face up. How are we gonna do that? I'm going to say let chosenIndex = I'm gonna make up a function
here, index of this card and have to write a function
here called index(of:) func index of card. It's gonna return an Int which is an index into this Array. So we'll return zero till
we go implement this. And once I have the chosenIndex then maybe I could say var chosenCard equals our cards Array at the chosenIndex. And then maybe I can say
chosenCard.isFaceup.toggle. Maybe this'll work. Seems to compile. It's probably good. We can even verify it's working
by printing the card out. So how do we print this chosenCard out? chosenCard equals ... in other languages you
might say %s or something and then have some string here. We don't do any of that in Swift. Swift has an awesome feature which is \(). In the middle of a string
that you are printing, you can put \() and in here, put anything you want that
will evaluate to a String. And most things in Swift,
will evaluate to Strings. Boolean, Int, Strings, even whole struct. If I put the whole
chosenCard struct in here, this can evaluate to a String because this struct is only made up of things that can evaluate to a String. Even our CardContent is gonna be a String. So let's just fix this index(of:) to actually find this card in our Array. And we'll be good to go. We do this with a simple for loop. I'm gonna say for index in 0..< my cards count. If cards at that index dot id equals, um, "of" (that's my argument right here, "of.id") then I'm gonna return the index. And if we get through this
foor loop and don't fail, then guess what? We return zero. That's the first card, that's bogus. We'll fix that later. Also, I don't really like "of". "of" is kind of a bad variable name here. So that's why we want to
be able to say "of card". Have internal name and external
name that are different. And we say "card" there. Do I want this to be "of" or _ here? Would I rather say
chosenIndex = index(card) or would I rather say
chosenIndex = index(of: card)? And I think the latter. Even though by the rules, putting _ here would probably be legal because this index is taking something that is clearly a card so it would be redundant to say card but it's not necessarily
redundant to say "of" if it makes this code read much nicer. We want our code be readable. So let chosenIndex = index(of:
card) reads pretty smooth. So it's worth the preposition here. Let's give our app a run
and see what's working. We see our console appearing. Let's try this first helicopter. Boom. This is really great. Look how it has printed out our chosenCard as a String. card.isFaceUp: false, isMatched: false, content: the helicopter, id 4. It's taken all of our vars
converted them to a String form and then added it all
up into a nice String that shows our whole card. And it even seems to be
working, card.isFaceUp false. That's good. Our helicopter was face
up when we touched on it but this is not actually working. And to tell you about that, we're gonna have to go
take a look at our cards. I'm gonna print the chosenCard but then I'm gonna print
out all my cards as well. \(cards) That's gonna print all my cards. Let's try that and see what we got here. I'm gonna click on this helicopter again. Boom. Let's see what we got. Helicopter is false. Oh no. All my cards are all at face up. face up, face up, face up, face up. Even both helicopters. Why are the Array of
cards not being changed when clearly it looks like the chosenCard got changed to face down. Well, I did this intentionally so that you really
understand about structs. When I say var chosenCard
= cards[chosenIndex], it makes a copy of this card. Even just assigning one variable
to another, copies things. structs are copied around
all over the place. If you wanna change this
card at the chosenIndex, you have to change it directly. We have to say, instead of
having this var chosenCard, we have to put this
cards[chosenIndex] right here and get rid of this whole
chosenCard intermediary which was a copy. So here, I'm taking the isFaceUp that's in the cards Array
itself and toggling it. But that has an error as well. Cannot use mutating
member on immutable value. 'self' is immutable. Oh, no. 'self' is my whole card. You're telling me that my
whole card is immutable? And by default, yes it is. And if we have a function like choose, that's going to mutate a card. We need to let the world
know by saying mutating. Putting mutating in front of a func tells the world that calling this function is going to change this thing. Therefore, this function
can only be called on a Model (a MemoryGame) that is a var. If we said private let model right here, then we wouldn't be able
to call choose down here cannot use mutating
member on immutable value. You see? So this is how Swift knows
to do that copy-on-write. And how it enforces the
mutability of things. We have to say this function can mutate, can change this struct. So we build that. It's gonna get rid of our error. Now let's run. Let's see if we're actually
changing our cards. Same helicopter, boom. It looks like it worked. True, true, true, true. That helicopter, false. Nice. Except my UI didn't flip over. What? How come my UI didn't show this base, let's try another card. Let's try the bicycle. It's faced down too,
they're both faced down. I'm not seeing anything. What is the problem? This is where all the work
we've done this week culminates in a very simple mechanism
that makes our UI reactive. All we need to do is
make sure that every time this Model changes, the UI rebuilds. Pay close attention here. This is where MVVM really
has its full payoff. We're gonna add three simple but powerful keywords to our code and turn our UI into a fully reactive one where any change to our Model
is instantly going to cause the entire UI to update to match. So how do we do this? We start with our ViewModel. We're going to make it
behave like something it's gonna behave like
an ObservableObject. ObservableObjects are the kind of objects that can publish to the
world: "something changed". When you say something behaves like an ObservableObject, it gets a var that you can't really see
called objectWillChange. It's of a certain type. a don't care, essentially. We don't care what this is because we're not going to actually define this objectWillChange. It's not like body var where
we have to implement it. It gets implemented for
us, but we get it for free. And we can use this objectWillChange to cause our ViewModel to
announce something has changed whenever our Model changes. So for example, when you choose down here, you can say objectWillChange. And the way you make the
objectWillChange publish that. If you say .send. This says, send to the world
that this objectWillChange. Its objectWillChange by the way, because maybe multiple things will change. And SwiftUI is great
at batching them all up into one redraw on screen. So that's why we want objectWillChange. Again, we don't need
this objectWillChange. You can call objectWillChange
send anytime you want. Anytime you change your
Model and you wanna advertise to the whole world that something changed, you can call objectWillChange.send. And we certainly would wanna do that here 'cause we're changing our Model. We want to let everyone
know the Model changed. However, it's even a
little cooler than that. We can take any of our vars
and put another keyword @Published in front of them. And in this case, anytime this changes, anytime anyone does anything
that changes the Model, it'll automatically do this for us. So we don't even need
that because model.choose choose is a mutating func. So SwiftUI knows that that changed it. And we will automatically
do objectWillChange.send. This is part one of our MVVM notifications to make it so that our ViewModel is doing its job to publish to the world "something changed" every
time its Model changes. The second part of it is having it so that our Views redraw
when something changes in their ViewModel. And to make that happen, we need to put another little keyword here which is @ObservedObject. @ObservedObject means that when this
says something changed, please rebuild my entire body. That's all @ObservedObject means. We have a little error here, it says property wrapper can
only be applied to a var. So we do have to make our ViewModels var because these property
wrappers, we're gonna talk all about property wrappers
next week or the week after but they require that
these have to be a var. Amazingly, that's all there is. Once we have these @ObservedObjects and we're observing our ViewModel which is an ObservableObject and is going to be publishing
"something changed" every time that the Model changes, now our UI every time we change it, like change isFaceUp, it's gonna redraw. Let's take a look. Well, our favorite helicopter. Amazing. I clicked it again, click all of them. They're working. Flipping the cards over. And that is it. That is MVVM. And this whole thing will work in our Preview as well in live mode. We'll resume. Very nice. That's it for this part of
the demo, we're gonna go back to the slides and talk
about enum and Optional. And after that, we'll come back here. We'll flesh out our choose
to not just flip a card over but to actually play
our full Memorize game. enums are kind of like structs and classes and that they're building
block data structures. However, enums only have discrete states. The value of an enum is one of some number of discrete values. For example, here, I've created an enum fast food menu item. And at our fast food place,
we only have four items. Hamburger, fries, drink,
and a cookie that's it. So an enum is a good data
structure to represent that because there's no other options. Now, an enum, just so you
know right off the bat, is like a struct in
that it is a value type. So as I pass it as a parameter, or I set one variable equal to another variable this thing is being copied around. Copied. We're not about reference types here. What's amazing or interesting, notable about enums in Swift is that each state can have
some associated data with it. For example, in our fast food menu item, maybe the hamburger has
the number of patties. Is it a single burger or a double burger? And the fries have the size of the order and the drink has the
what kind of drink it is. How big is it? And cookie has none. We maybe only have chocolate chip cookies so we don't have any associated data. Now in this example I'm giving you see the size is type FryOrderSize. That could be an enum too. case large and case small fries. Also, I want you to note
that in the drink case, it's got String and ounces: Int and then the String case is unnamed. But we didn't say that that
drink String is like the brand. Although that is what it is. We probably should have labeled it. But I just wanted to show here that it doesn't have to have a label, each of these associated data and that you can have
multiple ones there too. Drink has both a String and
this ounces, which is an Int. So all the items in our enumeration can if they want to have this associated data. So how do you set the value of an enum. We know what an enum
looks like to declare it. How do you set some
variable to have a value? Well, you just say equals <name
of the enum> dot the case. So FastFoodMenuItem.hamburger or FastFoodMenuItem.cookie. That's how you set it equal to it. And if it has associated data, you just put it in parentheses
there with the argument. If it had one, patties: 2. Now my menu item there, that menu item constant I
have is a double burger. You can leave off the FastFoodMenuItem. from one or the other side
of the equals, but not both. So you can say menu item equals
FastFoodMenuItem.hamburger or you can say other items of type FastFoodMenuItem equals cookie but you can't say you had
another item equals cookie without FastFoodMenuItem
being mentioned in there because that .cookie might
be on some other enum. There's just no way for Swift
to know what you mean there. So now we know how to declare an enum. We know how to set its value. How about checking the value? If I have a var, how do I check its value? We do that with switch statements. Again, other languages
have switch statements. Swift has commandeered
the switch statement to be the way we go and
check the value of an enum. We can use if statements if there's no associated data involved. But most of the time we're
gonna use switch statements to figure out which state
our particular enum is in. For example in this case,
it's gonna print out burger because our menu item
is a hamburger, a double and I'm switching on
it and case hamburger, it prints burger. So you just say switch, your enum variable and then case this, case that, case that, case the next thing. That's how you figure out which one it is. And for each one of them, you
can do whatever code you want. Here, I'm just doing
print, but you could do arbitrarily complex code there. Because Swift knows that you are switching on a fast food menu item here, you don't have to say case
FastFoodMenuItem.hamburger. FastFoodMenuItem.fries,
FastFoodMenuItem.drink. You can just leave that part off. This is again, part of
Swift's type inference. This is an obvious thing for
it to be able to do for you. If you have one of your
states, hamburger or fries and you don't wanna do anything just don't want to print out, for example, in that case. You can say break. break breaks out of
the switch right there. But it's really important
to understand though that you have to handle every
single case in your switch. You cannot switch on something and then not handle every case. However, there's a really
nice case called default and the default case will catch all the other cases you didn't specify. So in this particular
case here where we have our menu item: it's a cookie, it's gonna go through this switch. It's not a hamburger, it's not fries. So it's just gonna be the default case. It's gonna print other. Switch works on other types by the way. It doesn't work just for enums. You can switch on a
String if you want it to. Say case goodbye to something,
case hello to something. But if you're switching on
something like a String, you have to have that
default because remember, you have to switch on every possible case and a String has an
infinite number of cases. So you have to provide default when you're switching
on something open-ended like a String. And in each of your cases, they don't have to be one
liners like print hamburger. They could be multiple liners. But one thing to note, if
you make multiple line ones, they don't fallthrough to the next case. Whether you have one
line or multiple lines, they don't fallthrough. If you want fallthrough behavior, there is a keyword fallthrough
that you could put. For example, after where
it says print fries, it would fallthrough in there
and it would fallthrough into the drink case, but
we don't usually do that. In Swift, I don't really recommend it. It's anti-Swift to do it that way. But people coming from other languages where things do fallthrough,
maybe wanna do it that way. So they make, they preserve the option by having this fallthrough keyword. But what if our enum has associated data? Like how do we get the
patty count and the size of the fries and the kind of drink it is? Well, we do that. Same thing. Switch statement looks exactly the same, except in each case, we're gonna put parentheses
let and some variable name. And that is going to
have the associated data and put it in a variable of that name for us to use in the code
we're executing for that case. So for example, in this case right here, we have our menu item
is a Coke, 32 oz coke. It's gonna print 32 oz coke on the console 'cause we're
gonna get to the drink case. It's gonna have let brand and let ounces are gonna grab each of the two pieces of
associated data for that. And when we print, we're using the ounces and brand variables. So we just defined with
those lets to print it out. You're allowed to use different names here than are declared in the enum. When I declared this enum, I
used the variable name patties for the number of patties in the hamburger and for brand, I didn't even put a label for the String or the brand on a drink but when it comes time to
use it, I can pick any names I want to make my code
look nice right here. enums like structs and classes are allowed to have methods on them. Perfectly legal. And they can have vars but only
computed vars like var body where you drop in the function. They can't have vars that
have any actual storage because an enum storage is its cases. And if you're a case hamburger
with a number of patties, that is the storage. The patties, and the fact
that it's a hamburger, that's all the can be stored in an enum. And enum represents discrete value. So you can't have additional stored vars that you throw on there. If you needed additional stored
vars or something like that, you would put your enum
as a var inside a struct. If you do add a method to your enum, you might need to execute
some code inside that function which depends on what kind
of enum value you are. And you can do that by doing switch self. Here, I have a function is
included in special order. Number five or number three or number two which is a function on
a fast food menu item. I can ask any menu item, are you included in special order number two? And it executes this function. While inside there, I'm
switching on myself. If I'm a hamburger, then
special order number one let's say is a single burger. And special order number
two is a double burger. So I'm gonna see if the
pattie count that I have is that number. And then for fries and
cookies, I'm saying that in every special order. And for drink, I'm only gonna say it's included in the order. If it's a 16 oz drink, all the specials are 16 oz drinks. One other little piece
of info I wanna glean from this slide is that notice
when I was checking to see if a drink is included in a special order, I didn't care what brand it is. So you can have a Coke,
diet Coke, Pepsi, whatever in your special order. So to get at the second associated value, I just put _. Remember _ is always the placeholder for anything can go here. We're not paying any attention to it. So I just put that _ there. So I could put a comma
and get to the second one. That's what we can use underbar for when we're getting the values, the associated values of an enum. How about if we wanted to enumerate through all the cases of an enum? We wanna use a for in and
just like we can for in over an Array, it would be really cool
to be able to for in over all the possible values in your enum. Hamburger, fries, drink, whatever. So here I have an enum on Tesla
Model and it's got Model X, Model S, Model 3, Model Y. And I wanna be able to iterate over it. I can do that as long as I mark my enum as behaves like CaseIterable. Remember colon and protocol listed there. Is a way to say that
this thing behaves like. So if an enum behaves like a CaseIterable, then it will have a
static called allCases. allCases is just something you
can iterate over with for in and each time you go through the for in, you're going to be having
the next case in the enum. We've already seen an enum with associated values in our demo so far. It was when we were
creating that LazyVGrid. Remember when we created a
LazyVGrid, we had to provide the Array of GridItems that describe how our LazyVGrid is laid out,
how many columns it has, etc.? And we did some different things there. We said, GridItem.fixed(100). And we didn't really do
this, but we know the default is a GridItem that's .flexible. And of course, in the end, we
ended up using the GridItem which is .adaptive(minimum: 65). That's what our demo code currently says. Well, all of those were
just cases in the enum size inside the struct GridItem. The full name of that
adaptive thing we put was GridItem.Size.adaptive(minimum: 65). That was the full expression of the enum case that we use to say, which kind of GridItem we want. And this is perfect fit
here because there's only three different ways to specify how to do your LazyVGrid's columns. And these are what they are. Adaptive, fixed and flexible. So anytime you have only X
number of ways, three ways or five ways or two ways, you want an enum but each way needs different information. Fixed wants to know what
is the number to fix it at. Adaptive and flexible both wanna know the minimum and maximum
that they can live within. Also notice here, some of
the cases have defaults. That's why we were able to
say .adaptive(minimum: 65). We didn't have to say maximum something because the maximum there
is defaulting to .infinity. .infinity by the way is just
a var, static var, type var on CGFloat, which essentially
represent infinity. And you can see also here that the fixed case didn't
put a label on the float because if you say fixed
20, it's obvious you mean fixed at 20 points wide. So this is a great real-world example of an enum that has cases
with associated data. Now, I'm gonna show you
another enum that has a case with an associated value. Probably the most important
enum in all of Swift is called Optional. And Optional is a cause
for great consternation when students are first learning Swift, but an Optional is just an enum. Really nothing more than that. It looks like this. It's an enum with two cases, the case none and the case some. It's also a generic type,
just like our MemoryGame with generic, or like an Array is generic. It's generic over any type you want. So that T right there can be any type. And that type is the associated
data or associated value on the some case. Again, just like we had number of patties on a hamburger
being associated value. So the some case has an associated value which is that generic type. So where do we use this? We use it any time we have a variable whose value
can sometimes be "not set" or unspecified or undetermined,
something like that. These variables are sometimes gonna be in the of this
.none case of this enum. And they're sometimes going to be in the state where they're set and they're gonna have a certain value. That's just gonna be the .some case of our enum with that associated value. And this happens surprisingly often. In fact, it happens so often the Swift has introduced a lot of really good syntactic
sugar to make it really easy to use this enum up here. So let's take a look
at this syntactic sugar because that's the key to
really understanding Optionals is understanding how this syntactic sugar maps to this Optional enum we have here. Let's first talk about declaring a variable to be of type Optional. In other words, we want a variable whose type is this enum right here. You do that with a question mark. You just specify the type
T, the generic type String or Int or whatever, with
a question mark after it. So below you see, I have this var hello, that is of type Optional String. It's not a String. When new students first
see var hello: String? They think, oh, hello is a String. But no, hello is an enum. Hello is an Optional. String is just the type of the associated value that
comes with that Optional when it's in the set state. Really get used to saying when
you see this kind of code. var hello is an Optional String. Now, once you've declared a variable hello to be an Optional String,
you can set its value. And there's two different
values it can have because it's an enum with two cases. The first value is the
"not set" case .none. And we use the keyword
nil to represent that. When you see the word
keyword nil in Swift, it means Optional.none. The none case of Optional. You see the very bottom
line of this slide, var hello: String? = nil. We are setting that thing
to be the .none case of our Optional. The other thing you can say there too is the value you want the associated data for the some case to be. And so this syntactic
sugar that's what provides is you can just say var hello which is an Optional
String equals "hello". That's saying set hello, which is an enum to the some case with "hello"
as its associated value. And that's it. That's the only thing we
can set an Optional to. Either we set it to nil, which is none. Or we set it to the some case by specifying what we want
the associated value to be. One thing that's kinda cool about Optionals is that when
you declare them like this, you don't have to say equals
nil to start them off. Optionals always start
off with the .none case as their default value. So you see where I've put
the little circle there, the little oval, there's
no equals nil there. That's perfectly legal. We know that in Swift, all
variables have to have a value from the very time
they're initially created and that's true for Optionals as well. It's just that Optional
start out in the .none case. Again, that's just syntactic sugar. It's something that the
Swift compiler is doing for us to make our lives easier. So we know how to declare an Optional and how to set its value to
either nil or to something. How do we get the value out? How do we look at the
value, this associated value when we have an Optional? The syntactic sugar for that, there's two different
kinds of ways to do it. One is exclamation point. If I have a var hello and I say, hello! that means if this Optional
is in the some state, give me the associated value. That thing of type T. If this Optional is in the
none state, crash my program. That's what exclamation point means. That's what we call for forced unwrapping. Or unwrapping Optional, looking in there, grabbing its some associated value, but if it's none, we crash. We're forcing it out of there. This is exactly the same as
what you see on the right side of the screen there,
we're switching on hello. hello is just enum. It's an Optional, so it's an enum. And in the case of none,
we crash the program. But in the case of some, we use let data to get
the data out of there. And then we print it, cause that's what's happening
on the left arm thing. Print hello! Now some of you are saying, thank you for telling me
about exclamation point. I'll be sure to never use it because I don't want my
program to ever crash. But no, we want our program to crash when we're in development if we have a hello at
some point in our code and we are certain that
it's never supposed to be not set at that point,
it's always supposed to be in the some case, then
put an exclamation point there. And if it crashes, it
will crash in development, you'll find a bug, that won't
go out to your customers. If you work around it and
it don't let it crash, then your customers could get that really wonky behavior out there. Because you told me you are certain that hello is not supposed
to be not set right there. However, there are certainly many cases in your code where it's perfectly valid for an Optional to be
in either the none case or the some case, depending
on other circumstances. So the other way to get the value of an Optional is with if let. This is really elegant syntax
here, you're combining let because you're gonna
declare a new variable here to have the value you
want out of the Optional. And if too, because you're
testing whether the Optional is in the some case,
together to get the value. All I say is if let safehello, which is a new variable, equal
hello, my existing Optional, then print safehello. Otherwise, do something else. And in the other something else place, I'm not gonna access safehello because my Optional was not set. This is just like saying
switch on hello, the enum and in the case of none, we're gonna do the something else part. And in the case of some, we're gonna use let data
to get it out of there. And then we're gonna do what we wanna do. In this case, print that data. These are the two ways we're
going to get information out of an Optional. Either force it out of there and crash if it's not set or we're gonna use if let to safely extract it. These last two slides, the one where we're declaring
and setting the value of Optional and here where
we're gaining the value, that's 90% of knowing
how to use Optionals. There are a couple other minor pieces of syntactic sugar that
we're gonna take a look at but they're much less important. If you understand this
slide in the previous slide, you're gonna have Optionals. So the first minor syntactic
sugar is the question mark. Question mark operator, it does Optional defaulting, essentially. It's called the nil-coalescing operator. This operator is gonna have something on the left side of it, and something on the right side of it. On the left side is gonna be an Optional. And the right side is the value to use if the Optional is not set. So that's just like
switching on that Optional. And in the case of there's
none, use the thing on the right side of the
question mark, question mark. In the case of that it's some, use it's value, the Optional's value. Really nice way to just
have a default value of your Optional happens to be not set. Another piece of syntactic
sugar is Optional chaining. Here, we're gonna use the question mark when we're accessing the Optional. Not when we're declaring
it, when we're accessing it. And what this allows us to do is go through a chain of things
that are Optionals. And each time we get farther
and farther, we keep going. And if we ever hit something that's nil, we just give nil back to the
thing we're trying to assign. So here, I'm letting y
equal this expression. I'm starting off with x,
which is my Optional there. And if it's set, then I'm gonna
call the foo function on it, which also returns an Optional. And if that returned something that's set, then I'm gonna access the
bar var on that thing. And if that is in the set state, then I'm gonna access the z on that. And if any of those
things ever returned nil, then y just gets to be nil. So that looks like this. I'm switching and switching
and switching and switching. So first I'm switching on the x then I'm switching on the value of x, then I'm switching on the
value of that foo function. Then I'm switching on
the value of the bar var all the way down to
getting the value of z. And if anywhere along the way, something comes back as not set. Boom, y gets to be nil. So that's called optional chaining. Again, kind of Optional
use of the syntactic sugar. Believe me, you will start using that by the end of the quarter but early on, that might be a
little too much to grok here. So don't worry too much about that. All right. The best way to probably
really get these Optionals is to see them in action. So we're gonna go back to our demo now and we're gonna do two
things with Optionals. One, we're gonna fix that bogus index(of:) method that was returning zero. In other words, the first card. If it couldn't find the
card we were looking for, that is bogus. Of course it wants to return
not set or undetermined. So we're gonna make
that return an Optional. Then we're gonna go back
to making our Memorize game actually play that game of
Memorize and using Optional in the logic is going to
figure prominently there. So seeing both of these
things using Optionals hopefully will give you
a much more of a feel of how we use these things to our advantage when we're
building things in Swift. So let's hop right back
into that right now. We've got our application
hooked up beautifully now between our View over here,
our ViewModel and our Model. but our game only flips the cards over when we tap on them. We need to make it so that
it actually plays the game. But before we do that, we're gonna fix this bogus
return value down here. Let's remind ourselves
what this function does and why this is bogus. When we choose a card, we
need to find out which card in the Array we just chose
by looking at the identifier, the unique identifier of
the card and comparing it to all the cards that are in our Array. So that's what this function does. Just as a for loop that goes
through all of our cards and find the one that
has the same identifier. But if it goes through here and they can't find the
card that matches it, maybe someone passed us a card from a different game
or something like that. Something that doesn't have
an identifier that matches, then we return zero. Now, this is bogus because that means that if we can't find a card, we're just gonna return the first card. And that clearly makes no sense. That poor first card
did nothing to deserve being the return value
if we can't find a card. So what do we wanna return here? We essentially wanna return "we couldn't find the
card you're looking for". And we're gonna do that by changing our return type from being Int to being an Optional. This Optional Int can be nil or in the .none case,
if we can't find this. So instead of returning
zero, we're gonna return nil. Otherwise, we can just return an Int and Swift is automatically
going to turn that into an Optional enum in the .some case with an
associated value of this and it does all that for us. But that breaks our code
over here of course, because here, I have a cards Array and I'm trying to index it by
something that is not an Int. I just option clicked here
to see what type it is. And the type is Optional. Not Int. This is not the type Int, this is the type Optional who's associated value is an Int. We clearly can't index in
Array with an Optional. We have to index it with an Int. We learned two ways to
take the return value of this index here, this
Optional and turn it into an Int. So we can actually do it. One way is with exclamation point. If you put an exclamation
point after any expression that is an Optional, it will look at it, assume it's in the .some case, grab the associated value
and use that instead. And if it's not in the .some
case, it crashes your app. So this line of code will crash my app if this index cannot find
the card and returns a nil. I could also put this
exclamation point right here. I can put the exclamation
point either where I got it or I can do it where I'm using it. It doesn't really matter. Any expression that is an
Optional, like chosenIndex. chosenIndex is an Optional
can be exclamation pointed. But now, this line of code will crash if this function could not find the index and returned nil, the .none case. clearly here, we do not want to crash if we can't find the card
because this func choose, this is a public method. It's not private. So any code anywhere in
our app could call it and they could pass a
card here that is no good. And we don't wanna crash in that case. We just wanna ignore it. They're saying, choose a
card that's not an Array. We just ignore it. Maybe we might put an error out
on the console or something. We'll talk a little how to
do that later in the quarter. But we certainly don't want to crash here. So we're not gonna use
the exclamation point. Instead, we're gonna do it the other way which is if let. That version is just a matter of typing if in front of the let we already have and then putting some curly braces around the code you wanna execute if this Optional is not nil,
if it's in the .some case. Our chosenIndex only
will be set to something. If this is not nil and we're only gonna do
what's in this curly braces if this was in the some case. chosenIndex, this var doesn't
even exist outside here. It only exists inside this curly brace. Being able to do if let is just part of the syntactic sugar that Swift provides to handle Optionals. We're almost ready here to start playing the game instead of just
flipping cards over. But before we do that, I wanna show you that this function
actually exists in Array. This might be a little hard to believe but you can do this function in Array. We didn't even need this code right here. And that's something
you're gonna find in Swift. And one of the reasons you really have to familiarize yourself with the programming
interfaces, especially of Array, is that there are a lot of functions that will
do what you want built in to this Swift library. Let's take a look at how
we could use a function in Array to do this index lookup. And to make some more room here for us to maneuver, we'll
unpin our Preview there. We're also going to copy this line of code and comment it out so that you can see what
the old code looked like when I go in here and
replace it with the new code. The function we want in Array that does this looking something up is called firstIndex(where:). Functional programming strikes again here. This little function takes a card. It's gonna be called repeatedly once for each thing in the Array. And it returns a Bool whether that's the droid
you're looking for. That's what the where means. And it's called first index. Because even though our
Array can't have two cards with the same identifier,
that's the whole point of them is that how they're identified. You could have an Array of something else where that's not true. And so, there could be multiple matches. This is gonna return the first index where this function is true. Notice the little throws
in the middle right there? That's an air handling
thing that we're probably not gonna talk about until week
six or something like that. Not important in this particular
case, you can ignore it. So we need a function
here that takes a card. One of the cards in the
Array and in our case, compares it against the identifier
of this card and returns whether it was found. Open curly brace. I'm gonna make the
argument to my function. Be called aCardInTheCardsArray. And then I'm gonna check to see if that aCardInTheCardsArray's
id equals my card's id. This is the card that was passed to me. So this already reads
kind of like English. I'm gonna look in the cards. I find the first index
where that card's id equals my card's id, but
we can make this look even more Englishy because you're gonna replace the arguments to a closure with $0, and $1, $2, $3 for the other arguments and get rid of the Int and just say $0.id equals the card.id. And now this reads as
we're gonna look through our cards to find the first index where that card's id equals our card's id. Even more Englishy. Now this expression right here replaces all of this and all of this. Much cleaner code here. And now the moment has come where we can actually make our game play. To go back to our View
and pin the UI again. Let's work out a strategy
here for the implementation of how we're gonna play our matching game. First, obviously we want all
our cards to start face down. Oops, they all start face up. So let's go make them
be face down and resume. If we choose a card,
nothing really happens. We have no matching going on. But if we choose another
card, now we have to match. If we choose a third card, I want these cards to go face down. And we choose this card, they stay up. They'll have to be matched
another card to go face down. Now, if I choose two cards
that match and then click on another card, I'd still
want these to go face down and preferably I'd like them to disappear. Just go out of the game. But I certainly don't want to
be able to click on them again and have them come back up. I already matched them. That's how my game plays. Now, the key in all of this is knowing when we have to do a match. And that only happens when we click on a card and there's already
one and only one card face up. So if I click on this card, I have to try and match it right here. But if I click on this card, I don't have to do any matching. I just have to turn these face down. No matching required here. So I'm gonna start my game
logic by adding a private var which I'm gonna call the
indexOfTheOneAndOnlyFaceUpCard. An Int 'cause it is an
index into this Array. And you might think there's
a kind of a crazy name indexOfTheOneAndOnlyFaceUpCard and it might be a little bit
longer than it needs to be. We might be able to come up
with a nicer, shorter name but I'm only gonna have
to type it this once. After this, Xcode is going
to escape complete it for me everywhere. I could always rename it later. So my logic here really
only has to do two things. It has to keep this var up to date as the cards go up and down. And when there is one
and only one face of card and I flip a card up, I
have to try and match them. That is pretty much the
entirety of my logic. Now I added this and we have an error. It says return from initializers without initializing
all stored properties. Oh, you know what that means? You have a var, it's not
set equal to anything. What do we want this
var to start off with? When we start our game and
all the cards are face down, what is the index of the
one and only face up card? Oh, oops. There is no
indexOfTheOneAndOnlyFaceUpCard. So this is essentially not set. Well, what kind of type do we know? That's good for storing things
that are sometimes not set? An Optional of course. We'll make an Optional. And when there's not one
and only one face up card, this will just be nil. It'll be in the .none state. Notice that that got rid of my error because we get a free equals
nil for all Optionals. Any type of an Optional
you get equals no for free. We don't need to say it. It is initialized. This variable does start out initialized. It's initialized to Optional<Int>.none. Also known as nil. Now our logic is just to see if there is one of these and
if there is try and match it. So I'm gonna say if I can let potentialMatchIndex equal the indexOfTheOneAndOnlyFaceUpCard 'cause that's the only time
I have a potential match is if there's one and
only one face up card. Then I'm gonna ask and see if the cards at my chosenIndex, that's the one that user just chose. If its content matches the
potentialMatchIndex content. If it does, I have a match and I can just say that
my chosenCard is matched and the potentialMatchIndex'ed
card is also matched. Whether, I got a match or not, there's clearly two
cards face up right here. So the indexOfTheOneAndOnlyFaceUpCard now has to be nil. What if there wasn't one
and only one face up card when we chose this card? That's the else case right here. In that case, we need to
turn all the cards face down because either they're
already all faced down or there's two cards
face up that either match or don't match, they need
to be turned face down. So how do we turn all our cards face down? for index in 0..<cards.count. cards[Index].isFaceUp equals false. This will turn all our cards face down. Now the card we just chose is gonna get turned face back up because I left in this isFaceUp.toggle. We're still gonna flip that card face up. No matter what we do. When we click on a card that's faced down, we wanna flip it up. While we're here, I'll talk about cool
little thing you can do. If you find yourself
saying 0..<array.count, you're gonna replace it with
the Array's indices var. This indices var on every
Array return to this Range here of 0..<cards.count. It looks a little nicer to do
this, not a huge difference but it's kinda nice. In this case, I just turned
all these cards face down until the chosenCard that I just chose is
gonna be turned face up. So that means that the
indexOfTheOneAndOnlyFaceUpCard is the I just chose. And this is the entire
logic of playing this game. There is nothing more
to it really than that. The only last couple of things I wanna do is that if you click on a
card, that's already face up, I wanna do nothing. When we were doing testing,
we had clicking on the card and turn it face up, face
down, face up, face down. But in the real game,
if you click on our card this already face up, you
don't turn it face down. It just does nothing. So here where I get the chosenIndex, I'm going also check and I can do that with and that the card at the chosenIndex dot isFaceUp is not true. So I'm only gonna do all this stuff if I'm clicking on a
card that was face down and I'm turning it face up. But we have an error here. It says, cannot find chosenIndex in scope. I just said, if let
chosenIndex right there, chosenIndex Well, it turns out you can't use and in an if statement, if you
start out with the let. But you can use a comma. If you have an if
statement, you can separate the terms of it with commas and it means the same thing as and except for that it will
actually do this if let, before it does this one. And so chosenIndex will be defined here. The other thing is if I touch on a card that's already been matched. I don't wanna do any of
this matching either. So I'm also gonna say,
and not cards[chosenIndex] dot isMatched. Choosing cards that are already face up or that are already matched, is not gonna do all
this matching business. It just ignores it. Now this line of code got
really long, started wrapping. We can make it look a
little nicer by just putting each of the comma terms of this expression on their own line. I think to look a little
more readable here. We're almost there. This final error here. What's this say? Binary operator equals equals. So this cannot be applied
to two CardContent operands. I'm trying to see if the
CardContent on one card is equal to the CardContent on the other. Oh no. The equals equals not
working on CardContent. Well, what type is CardContent? It's this don't care. This CardContent could be an arbitrarily complex data structure. So it won't necessarily
just be able to be =='ed. How are we gonna deal with this because we want this to be a don't care but we also wanna be able to do ==. I'm gonna give you a preview of next week where we're going to to talk
about how to take a don't care and turn it into a "well, I
actually do care a little bit". We do that with a where clause. Well, I'm gonna say where CardContent behaves like Equatable. This Equatable behavior means you can do == on it. We're gonna talk all about
this next week in detail but it's the same kind of
thing as behaves like a View or behaves like an Identifiable. In this case, our don't
care type has to behave like an Equatable. It has to do ==. I've turned this don't care
into a "I mostly don't care". We're doing that all
over the place in Swift. This is really a heart of this protocol-oriented way
of design that Swift supports. For example, back in our
ContentView, look at ForEach. What is its argument here? That is actually a don't care. It could pay an Array of anything here. However, that Array of anything needs to be things that are Identifiable. So you can be sure that
ForEach, when it's defined, it defines as don't care with a where clause that says the items are behaving like Identifiable. And that's exactly what we're doing here. We're making our don't care
behave like an Equatable. So you simply can't create a MemoryGame with some data structure that does not have Equatable CardContent. It won't work. Really no such MemoryGame exists for creating a card game where
the content is not Equatable. Now we're done. Let's take a look. Let's see if it's working, resume. Choose this card. Okay, that's good. Now indexOfTheOneAndOnlyFaceUpCard is presumably index zero. And this card. All right, there was an
indexOfTheOneAndOnlyFaceUpCard We tried to match it, it didn't match. indexOfTheOneAndOnlyFaceUpCard
is now nil, not set. And that's right, because
we have two cards face up. So now when we click here,
oh, it turned those back down 'cause we got in here
indexOfTheOneAndOnlyFaceUpCard was nil so this if left did not do this, instead, it did the else and the else turned all the cards face down. And of course the chosen,
when it got turned, face up. Now our indexOfTheOneAndOnlyFaceUpCard
is this chosenIndex. So that if I click this one,
it does the matching again. In this case, hopefully it's set isMatched to true on both of them. Now we'll choose another card. It turned those face down, excellent. Now I'm gonna try and choose one of these. Oh, I'm tapping on it. It's not doing anything. That's because we don't
allow tapping on cards that are already matched. See that? If we chose an index and it's not matched. That's good. Although I don't really like them hanging out here by themselves. We'll fix that in a second. Let's keep going with our logic here. Got this one face up. I'm gonna click this one. They don't match. This one, these go face down. This one, this one, these go
face down and here, a match. Click those go face down
and they're matched. So I kind of click here. I'm clicking, clicking,
clicking, nothing, click here. Now the only thing I
didn't like about this was that my matched cards are still showing on the screen, even though they're already matched or kind of out of the game. Be better if they just didn't appear. So let's go back to our View and have our cards draw
a little bit differently if the card is matched. The way I'm gonna do that is I'm gonna say else if the card.isMatched, then draw the shape opacity zero. .opacity is a View modifier that says is how transparent
the entire View should be. .opacity(1) is fully opaque. .opacity(0) is fully transparent. So by drawing the shape with opacity zero, I'm still taking up the
space that a shape would use so that my cards don't move around a little bit when they disappear. But it's gonna show nothing
just literally transparent here. Let's see if that works. We'll resume. And we'll click here. And the helicopter, bicycle, they're gone. This one, no. Here, here, gone. Excellent, that is it. We have completed our game. We've reached the finish line here. And just like I did last
week, I'm gonna do a quick zoom through review of everything we did in lecture this week. We learned all about MVVM. We learned how to make
Models like this one. We found out that these
Models are UI-independent. That's why we only import Foundation. And why there's no reference to anything in the Views or
the ViewModel for that matter in our Model code. Our Models have our
data like all the cards and our logic in this
case, the choose function which is doing the matching
and in your homework, you're gonna have it add
scoring and things like that. We also learned that the
Model is of the truth. All the information about the cards, whether they're face up,
whether they're matched, et cetera, is all here in the Model. It's not stored anywhere else. Then we learned that
the purpose of our View that we've been building
is to reflect the Model. These var body: some View, the definition of this
variable is give me a UI that reflects the current
state of the Model. We also were able to get rid of our @State that we had in our CardView 'cause we learned that we
don't store state in our Views. We store them in the Model. One really great thing we found out about our Views is that
they're declarative. So ScrollView, here it is. The LazyVGrid here it is. The cards, here they are. As our code unfolds, so does our View. What we declare here is what appears here. And what we see here
is completely reactive to what's going on in our Model. And we accomplish all that
by introducing our ViewModel which binds the View to the Model. This ViewModel can also
serve as an interpreter. Now, our ViewModel is very simple. It doesn't really do much interpretation as it provides information
from the Model to the View but it could, if this was
a very complicated Model, this might be very
complicated code in here. ViewModel is also the gatekeeper
for Model to the View. It makes the Model private
and exposes information about the Model to the
Views only as they need. The ViewModel also enables the entire reactive architecture. It does this because it
notices changes in the Model. And by the way, I haven't
emphasized this enough, I think. The reason that we're
able to detect changes in our Model is because
MemoryGame is a struct and Swift is able to
detect changes in structs. You can't do it in classes but it can detect changes in structs. So that's how our ViewModel knows when something changes in our Model. Our ViewModel then publishes
anytime it sees a change here. And it does that because it behaves like
an ObservableObject which means it has this hidden
variable objectWillChange. Where you can say send on it. And this send notifies the
world that this has changed. We also learned that we don't actually
have to explicitly do that if we just @Published any
of our Model variables. @Published means when this changes call objectWillChange dot send, let the world know something has changed. Then our View, because it
marks its viewModel var as an @ObservedObject,
we'll get its body rebuilt. This happens automatically by SwiftUI. And it happens efficiently. Only Views who actually changed in here will have their body rebuilt. So @ObservedObject changing. We'll rebuild this body, but when it comes to rebuilding the body of this ScrollView and this LazyVGrid and these CardViews, it depends on whether
they've actually changed. For example, our CardView takes the card. Here it is. These are both structs. The CardView itself is a struct. This is a struct. Swift can tell if structs have changed. If these have not actually
changed as a result of the something change that
came from the ViewModel, then this body will not get rebuilt. And this is where Views are expensive. Views are not expensive
just to create the View with this one tiny little var. They're only expensive if you
actually rebuild their body. But we can take advantage
of the fact that Swift knows whether structs have changed
to not rebuild the body if nothing has actually
changed in a specific card. Finally, we learned that
when we tap on a card or otherwise do some
multitouch event in here, the way that that gets back to our Model is through
these Intent functions. So when we get a tap gesture, we express our Intent as
a user to choose a card in our ViewModel. And our ViewModel in turn, figures out what to do in the Model. In our case, we have a simple Model so it's just calling
choose in the Model as well to get the Model to change. And the Models choose,
which is a mutating function because it's going to change
the Model, does whatever it needs to do, its logic, et cetera. And all the changes that makes
in here might cause its cards to be changed, which causes
the MemoryGame struct to be changed, which gets noticed
here because Swift notices when structs change,
which gets @Published, which causes objectWillChange to happen with the ObservableObject,
which causes our @ObservedObject ViewModel to cause this
body to get rebuilt. And we're gonna recreate
all these CardViews but only build the bodies of the ones that have actually changed. And that is MVVM in a nutshell. And that's all we have for
this week in your homework. You're gonna replicate
everything that we've done again. And just like last week,
you'll add some stuff to make sure you really understand
what's going on in here. Next week, we will learn a lot more about the Swift language. You're still learning that. And of course, we'll learn
much more about SwiftUI and the powerful things it can do. And in next week's homework,
not this week's homework but next week's homework,
you will be on your own to write your own card
game app from scratch. - [Narrator] For more, please
visit us at stanford.edu.