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