Transcript for:
Swift Programming and GitHub Essentials

I like to start my lectures sometimes by looking back at the last lecture and doing all the things I forgot because you see how these lectures go. There's a lot to cover and sometimes I'll just go over something quickly. So I like to start and go back. One thing I want to clarify a little bit is this whole deal about the inspector.

Remember I told you you can go into select mode down here and then you can select things. A couple of things about it. One, I talked about why is this valuable if you know how to...

code why would you want this and I use the examples of localizers for your user interface designers who might not be coders that doesn't mean it's not totally of value to you as well it might be good for like fine tuning and one thing to note about this I don't think I mentioned this is that the selection works all the way across so if I click in here see it selected that line of code that was this thing I selected and it also selected here in other words it's not just the case that you select over here and it's like sure if you select in either of the two it selects the other one. So I wanted you to understand that. And then, you know, as a programmer, why you want this is you might want to be fiddling around with the color or some of the padding or something like that without having to be constantly typing and, you know, editing your source code. Kind of depends on what you like. Me, I love editing source code.

It kind of blows out of my brain. So I like being in the code, but some people might want to click buttons. So I just didn't want to undersell the inspector and the select. mode here it can be kind of interesting and valuable so that was one thing I wanted to go back to another thing I wanted to mention when we brought out the navigator and we went to the files area here I said look there's three files memorize app content view and assets there's actually another thing you can click on here we just sort of a file which is your project settings that top thing this is the setting see it's like what destinations here I'm building iPhone iPad and Mac designed for iPad and what I'm deploying it to iOS 16.4 and you can see that they're signing and capabilities that's where you deal with your Apple ID connection and all that so there is project settings here you don't need to go here much I'm almost never gonna reference this but just so you know there's a lot of stuff going on there you can feel free to browse through all these tabs and see what this does Xcode is a very powerful build engine for building multiple things that have dependencies frameworks and apps all depending on each other And so it has a lot of functionality in here for managing that kind of complexity. Another thing I wanted to mention is that this colon view, which all of you now should be very comfortable with the phrase, it means that ContentView behaves like a view.

This is, again, nothing to do with object-oriented programming. 0%. That is not its superclass or something. There's no superclasses in functional programming. That's not object-oriented programming.

That colon view means this structure behaves like a view. And we're going to expand on what it means to behave like something a lot in the coming next couple weeks, actually. But I just want to emphasize that. One other thing I wanted to show, just to emphasize a little bit about how some view works, I have a nice little demo. So I'm going to take this out of here for a second and just make it so that our body is just a text that says hello.

All right? And so you understand this has got the hello. You understand that that subview that some view there is what? What type is this? This is the type text because it looked inside the curly braces.

It saw there was a text in there and it said that's it. So notice that I changed that to text. It compiles.

It's right. Some view is a way to get the compiler to help you, but it's still basically doing this. And if I were to put something else in here that's not a text, like vstack that had this text thing and maybe another text thing.

Hello there. Put this down here. Now the compiler is going to complain because that computed little code there that computes the value of that property is not of type text.

So now you have a mismatch. In fact, if you look at the error there in detail, it's very illustrative. It says, I cannot convert the return expression of type VStack tuple view text and text to return type text. Notice how complicated the type is. that is returned here, that's actually being returned here, VStack, TupleView, Text, and Text. I told you that the VStack's little bag of Lego was called a TupleView.

You can see it right there. And specifically, it's a TupleView that has two texts in it. All of that is kind of encoded into the type for that VStack expression.

That's why we need some view, because it would be a pain in the neck if we had to know all this detail. Good news is you don't need to know any of this. In fact, in this course, you will never type TupleView.

That's something that the View Builder mechanism does behind the scenes for you. That's something you have to know about or type or ever access. Completely done for you.

And that's why we have this really wonderful sumView thing, which will look in there and, whew, it compiles. It works fine. You see, it figures out that it's a VStack tuple view of two texts. I just wanted to make sure we understand that a little more. So let me get back to where we were, our stuff.

There it is. And, oh, another big thing I forgot. I forgot to commit this to GitHub. I've been watching you.

Most of you, more than half of you have already done all of this, but for the half of you who haven't, we've been making a ton of code changes here. I'm just going to go to source control and commit to commit these changes to my repository. And you can see that along, this puts this nice window up that's going to show you every change you make. Now, here, there's a ton of changes.

All the files are on the left. That's because this is my initial commit. I haven't committed anything, so everything has changed, right? My whole project is new. But I still always want to put a comment.

In fact, it won't let you commit without putting a comment here. So what would be my comment? Well, I would say this is my initial commit for one thing.

But I'm also going to put in something that reminds me what was this commit about. If I had remembered to do this last time, I would have said this commit was ready to start doing Memorize. And I would have committed it before we started.

But we actually started. We made some cards. So now I'm going to say. that this commit includes the creation of CardView, and we did the isFaceUp for the CardView. So I'm just putting things in here just so I can quickly remember, what was that commit?

Now, I can commit here, just commit these 11 files, all my files, basically. Notice that I can also push to my remote all in one step if I want to, but I'm going to do this in two steps. Here I do my commit.

It's committed. Now when I want to submit my assignment, because that's what you're going to do, I'm just going to go source control push and because I created this project inside that repository that I cloned it sees that up there that origin main make sure you always push to origin main never to origin feedback I'm going to show you what that origin feedback branch is in a second don't worry about the tags we're not going to be doing that and just push and so it pushes it up there it's connecting here to github if I now go to github I would see the thing I just pushed You'd actually be able to see the files that are there. And then the next time we push, and hopefully I'll remember to push again somewhere in the middle of this demo, we'll be able to see the diffs. And you can see the diffs in Xcode before you submit.

And when you go to GitHub, you'll see the diffs, which you may be. That's really kind of cool, especially if you remember to do it, which I did not. Now, that feedback branch, if you look right up here, the top there, see it? It says Feedback.

If you click on that, there's a pull request. And the pull request, you can click on it here. and it's essentially going to pull this stuff from this branch, this feedback branch on GitHub.

And you can actually look at this. This is your grading feedback. So when your TA is grade, it will start appearing here. So anytime you want to look at that, you just go up there to that number one feedback, and it'll go to this. It becomes a tab.

You see it along the top. So you can go back between your code and the feedback you got. It's really kind of cool. All right, so that's the GitHub integration.

Not a lot more to say about that. This is the first time we've done this. It looks like it's working really well.

I think I had one person say, I couldn't clone the repository. One thing also, once you start committing along here, as we start making changes and we edit our code, you'll see it says M over there for modified. Or if you add a file, it'll say, A, you added a file. So you can kind of see what you're doing.

The first thing I wanted to talk about is something we introduced last time. I'll write it up here again. This is the trailing closer syntax.

And that is this thing right here. We had this ZStack and it has an argument there, content, which takes a bag of Lego and it ranges it. And it even has some conditional in there, depending on whether it's face up or not. It kind of does a little different list of views. And we also will remember, hopefully, that ZStack could have other arguments like alignment.top.

Look, our ghost, he's now aligned to the top. They're still stacked on each other, but the ghost is up at the top. This is just two arguments to the ZStack.

creator. The stack is just a struct, behaves like a view, and it has two arguments. If the last argument to any creation thing or to a function is a function itself, like this curly brace, remember this bag of Lego thing is actually a function that returns a view, one of those tuple views or something like that, a collection view. If the last argument is a function, then you can do this thing where you get rid of its label, if it has one, which they usually would in that case.

close it off, and let the function essentially hang off the end. See, it's just kind of hanging off the end there. That's called trailing closure syntax, because this function is called a closure.

We're going to talk all about that. These inline functions, we call them closures, and it's trailing. It's at the end. so it can hang out there.

So that's why it looks like that. And then we went along and said, well, we don't want the alignment at the top, we want the alignment to be in the center. And this alignment center, which is what we want, just happens to be the default, just like face up here, its default is false. The alignment thing in ZStack, it's one, is center.

So I didn't need it. This is fine, compiling, everything's good. And then I went one step further and I deleted those parentheses.

Now that step I can only do if I have a trailing closure syntax. You can't delete empty parentheses of a function call or a creation of a struct unless you've got a trailing closure syntax. Just want to make that clear.

While we're down here, by the way, I also want to talk about fill. You see this rounded rectangle? It seems to just be sitting there and it's the back so we know that it just does a fill.

This is really kind of doing this. dot fill. Just like we have dot stroke border is causing this rectangle to have its edges stroked, fill means it's filling. It's just that this is the default.

If you don't tell a shape to either fill or stroke, it will fill. That's just its default. And fill is kind of nice. It also will take what to fill it with.

In this case, I'm filling it with the color white. That's our little background there. Now I want to go back to where we were when we left off.

We had just Added this is face up thing here and we gave it a default value and I just want to emphasize that if I took this Default value off I get all these complaints up here Right and that's because I told you that if you have a bar in a struct any structs not a struct It behaves like a view any struct if it has a bar that has no value that's not allowed So if you want to create this struct you have to provide this value. That's why The top line doesn't complain. It's providing it. The other lines are complaining because you're trying to create a struct that's have a variable that's never had its value set.

Everyone understand what's happening there? And if I go and copy this is space up true and put it in these other ones, they'll stop complaining. So I just wanted to be clear.

That's why we are providing that argument. We're setting this variable that has to be set. And then we went to... the step of saying, okay, but most of the time, we want this thing to be, let's say, true. We want the card to be face up by default.

That allows us to take this out and still have that card be face up because it's defaulting. Let's talk a little bit more about the bag of Lego. We're passing to this ZStack to make our cards.

There's, we know that we can do lists of views in here and we can do these conditionals in this weird ViewBuilder bag of Lego world. There's one other thing we can do, which is we can do local variables. So I could say var base, for example, of rounded rectangle type equals this rounded rectangle that we use three times. This is really bad because this is, you know, replicated code. So I'm going to put this up here.

And instead of having this, I'm just going to replace it with that variable. Three times I use it. You see what I've done here?

Create a little local variable. Now you might be like, oh no, that's a View. But Views are just structs.

This could be an int and you wouldn't be so freaked out by this. Int is just a struct. Rounded Rectangle, which happens to be a View and a Shape, it's actually a couple other things, it's still just a struct. So there's no reason I can't create it here.

I could also say var i colon int equals one. That's perfectly legal. So I can create these local variables. However, I cannot go so far as to say var x is an int equals one and x equals x plus one.

Now I'm gone too far. This kind of code can't be in a ViewBuilder. The ViewBuilder only knows how to do the ifs, the lists and the variable assignments right there.

That's all it can do. It can do some other conditional type things. There's a switch statement, which is like an if with multiple cases, it can do that as well, but essentially conditionals, lists. and local variables.

That's all I can do. So let's take this back out of there because that's bad. Now this gives me a great opportunity to talk about another syntactical thing which is that we would never say var base of type rounded rectangle equals that. We would say let this keyword exactly like var except for this thing will never change. So let means this is a constant.

We don't use var because it can't vary, right? Very variable means it varies. And it reads nicely.

Let base equal this rounded rectangle. And it's a constant. It will never change.

And that's true. We never do change base. We, you know, create other views off of it by sending it the modifiers, but we never actually change it.

And we can't change it because I just told you views are read only. So we couldn't change it anyway. But inside of a view builder, you would always be using let because nothing in there can change. You can't have variables. Those values are changed.

You can't say x equals x plus one. You might say, oh, can we do the same thing up here? Let isFaceUp because we don't change it isFaceUp in here, but this is not going to work because if you say let isFaceUp bool equal true, then you're saying isFaceUp is true and it's always true. So now the guys up top trying to call it and say, oh yeah, please create me a card view that starts up face down. They can't do it because the isFaceUp is a constant.

It can't be changed. If we said we didn't want to have a default here, then we could have it be a let. Because then the one and only time it gets set, its constant value is what the person passed in. But again, now we're in the problem where you have to pass it in in that case.

If you want to be able to say equals false or equals true, this has to be a var. And the reason it has to be a var is it actually is going to vary. It's the one time you can actually change something in a view. The other reason is it starts out. with its default value, but then it can be changed when it's created.

But that's it. Can't change after that. So that's why we make it a var.

Other times, we just make the decision between let and var by whether we're going to change the thing. And if you want some advice, always start out with let. And the compiler will complain if you try to change it. Think of let as really the way you define variables. Var is more like marking something that you are going to change.

And it's great because when you're reading someone's code, you can tell they're going to go change this thing somewhere. in this code versus this is just a constant essentially. Now let's talk about another cool syntax thing here, which is this line of code, it's kind of annoying, roundedRectangle is roundedRectangle, I have to say roundedRectangle twice?

Why would I have to do that? Well, I don't have to do that. I can just go here and get rid of that.

Now this is called typeInference in Swift. I'll select this line for recording. And what's happening here is that Swift knows you've set this thing to a rounded rectangle, so it will infer that the type must be roundedRectangle.

And in fact, if you hold down Option and click on a variable like base, it'll tell you what it inferred from what the code says. And look at that, it says base is a rounded rectangle. And it is. And we almost always let Swift infer, as opposed to explicitly putting the type.

Why do we do that? Well, what if I decided, you know what, I've decided I want my game to have circles. as the background, which is kind of cool.

It actually looks kind of fun. All I had to do was change that round, rectangle, circle. I didn't have to change it twice.

Round, rectangle, round, rectangle, circle, circle, one time. Swift is what's called a extremely strongly typed language. Swift knows all the types at compile time in your entire code, basically. So, Since it's so strongly typed, it can really tell you if you're messing up and trying to pass the wrong kind of type variable to the wrong function or whatever. It's very strongly typed.

Well, very strongly typed languages can be very annoying if you have to be constantly expressing types. And for those of you who know languages that are very weakly typed, JavaScript, for example, right? You're just setting variables to anything you want and passing them off to functions. And if it's not working, it's just not working. That works maybe if you have a lightweight kind of...

But in a serious app you wanted some provability you want to make sure that your app is actually doing what you want it to do What Swift does as a trade-off for being so strongly typed to make your life easier is it does? enormous type inference Every time you think it could figure out what you mean It will we are rarely going to be specifying types the times we specify types are mostly the types of ours We're going to express this type because we want the compiler to type check what's returned here against this. Also, the arguments to functions. When we have a function, we're going to be very clear about what types the arguments are.

But when we have local variables on top, we don't do it. In fact, even right here, look at this. Is face up bool? Nah. Just is face up equals false.

False can only be a bool. That's the only struct that can have that value. So it knows is face up.

And if I do option click. it says, oh yes, this face up, that's a boom. Let's go back to rounded rectangle here.

We can just leave that off though. Next thing we're going to do is make it so we can tap on these cards and flip them over. Obviously, the thing we want to be able to do. This turns out to be incredibly easy and swift, as you might imagine, because when you build a user interface, you're tapping on things all the time.

How do we do this? We do it with a ViewModifier, and I'm going to put this ViewModifier on this ZStack, which means if I tap anywhere on this card, face up or face down, anywhere typing on this ZStack, it's going to do something. The name of the ViewModifier here is called OnTapGesture.

Basically looks like this, put it on some line by itself here, but I'm sending it to that ZStack. Now, this onTapGesture takes a function to execute when someone taps on this view. This really looks like this, of course. I think it's called, yeah, perform colon with this, right? That's really what this looks like, but I'm just using trailing closure syntax, right?

Right here, that trailing closure syntax works with everything, not just structs of their views anytime. So... I don't have to have that on there. It also has other arguments like count colon two.

That's a double tap. Two taps would cause this to happen. This little function right here, this closure that gets executed when I tap on it, it's not a view builder. It's normal code.

I could say x equals x plus one in here. It's just absolutely normal code. I can do anything I want in here, actually. It's just like it's calling a function. In fact, it is calling a function.

It's just the function's inlined right here into the middle of my code. So what am I going to do here? Well, first thing I'm going to do, I'm going to have it print.

Print tapped on this thing. Let's do it, right? I'm going to go over here and tap. That doesn't seem to have done anything. I don't see anything happening.

Well, that tapped is coming out down here. I told you you could pull this thing up here. Now, let me warn you that what I just did only works if you are running at least Xcode version 14.3, which came out last Friday. Okay, so this is a really cool just installed feature. And what this feature lets you do is see down here, it says previews and executable at the bottom.

You can actually click previews and tapping in your live preview will cause things to happen on the console if you use this function print. In the old days, meaning before last Friday, you'd... This wouldn't be here.

It would be as if this executable were clicked. And the only way to get the print to print on the console, the only really realistic way, you had to run the simulator, which is kind of annoying to have to go off to that simulator when I got my preview right here. I really encourage all of you to get up to 14.3 because this is going to be a really valuable debugging feature for you.

One thing about this declarative user interface, it can be a little hard to debug sometimes because you're used to imperative code where you're kind of... going through a sequence of events and you want to set a breakpoint see what happens move on whereas here you're just kind of declaring that these things are the way they are so being able to put prints in there is nice where you can see things being created right put a print in a creation method or something like that as they're going along and kind of verify that this is being declared in the way that you expect it to be so print statements are your friend especially for the first three or four weeks of this quarter really focus on prints as a really great way to debug what's going on, print out what's happening. And with 14.3, you can do it in your previews.

All right, so that's good. We know that this onTapGesture is working. I clicked on it, it said tapped. Now we want to flip the card over.

So let's put that in here. IsFaceUp equals not IsFaceUp. All right, does everyone agree that would flip this card over? This thing is played by, oh, oh no! Cannot assign property.

Self is immutable. Well, I promised. I told you this is the way it's going to be. This view, self, is the view itself. It's immutable.

Can't be changed. So we can't change. And even though that is face up, it says it's a var.

That varness is only at the very beginning, like I said. So now what are we going to do? Okay, we're really in trouble because I really want to flip this over.

Well, first of all, let me tell you the real answer. The real answer is R. Back end, our game logic is the thing that's going to flip cards over.

But we don't have our game logic. So how do we want to do it? Well, what we're going to do is a little trick that you can do in Vue Structs.

If you have state that you do want to have change, but it's only for temporary state, like if you're in the middle of an animation, you want to keep track of the beginning or the end of the animation or something like that. It's not for storing the state of your game. You never would put this.

in this mechanism I'm showing you. And the way you do it is you find the var that you want to be able to be changed and you put at sign state in front of it. So this is only for small things, kind of like this, just flipping the card over.

Now, some might want to know, well, what is that actually doing up there, at sign state? And I'll tell you, you can ignore what I'm about to say if it bothers you. You don't really need to know about it. It's creating a pointer to isFaceUp there. And at sign state is actually creating a pointer to a little piece of memory where it keeps isFaceUp.

So now the pointer never changes, the pointer itself. The thing it points to can change. but the pointer never changes.

So it satisfies that thing where the view can't change, but the isFaceUp can be changed. So that's what's going on there. Again, I'm really emphasizing our real state about whether cards are face up is gonna be in our game logic.

So we're not gonna use it. We're gonna be getting rid of this atTimeState next week when we have some game logic. So let's see if it works. I'm gonna tap on some of these.

Woo, look at that. Works perfectly. And all that is doing is just changing that isFaceUp there.

in the CardView. By the way, one thing that takes some getting used to too is you're used to other language, things like bools and ints and strings are like these fundamental types. This isFaceUp, like almost everything you're seeing, is a struct, so it can have functions on it. For example, bool has a function called toggle. Toggle changes the value of it from true to false, false to true, back and forth.

So this is how we would write this, isFaceUp.toggle. The other way is fine too, but. We would call this function on a bool to do it because it's destruct.

All right, so I'm actually going to write up here views are immutable. The thing that's changing about views, you know, their body can be changing and being called and reevaluated all the time. But the views themselves, the bars in the view themselves are immutable.

And that leads to the other thing I'm going to put here, which is at sign state. And I'm even going to put temporary state. Date that has to do with just kind of displaying, not about our actual app and what's going on. Let's go ahead and commit here.

Let's commit, I wanna show you this. Let's source control commit. Now, when we commit here, you can see the list of our files on the left.

Only a couple of them has changed. Only one of them, actually some code. And look at this, it's really nice. Shows you all the changes you've actually made here.

And what did we do here? Well, we added toggleable is face up. That's the main thing we added here.

I'm going to go ahead and commit that. Now over here, it's cleared. It'll have cleared the Ms off of here.

And I still haven't submitted my homework assignment. I just committed it to the repository locally. Okay, that's what commit does. We're going to push our homework assignment at the end of this lecture. Our app is really coming together here.

We have these very tall, thin cards. Not what we want, but it's close. It's getting there. Unfortunately, they're all ghosts.

So this is a really easy game. because all four of the cards all match each other. So we need to be able to have different things on there than ghosts.

So how are we going to do that? Well, there's just really no way around the fact that we need to tell our card view to draw something other than a ghost. Right now it draws the ghost because of this right here.

I really want to pass it along as an argument. So I want to do something like content is a ghost. Put the ghost up there like that. and then have it use that content in here. So hopefully everybody instantly sees exactly how we would do that, right?

We're just gonna have a var here called content. It's a string. There's absolutely no reasonable default for the content.

We have to know what it is. You can't have the default be a ghost, right? Cards that don't say what's on them, how to get a ghost, no. So it makes no sense.

So we're not gonna have a default there. Because there's no default, this wants to be a let. It can't be changed, so it has to be a let.

And then up here, content is working. It's already working. And then we'll use this content let instead of our ghost right here. So content.

For all of our cards, we can just give them each different content. Could be getting rid of this face up here if we wanted to, but we'll hold on to it for a second. I'm going to change this.

This ghost is kind of a Halloween thing. You saw my theme I built over there of these cards. These are Halloween things. So let's go get some more Halloween things from edit emoji symbols here.

I got some queued up here. There's a pumpkin and we got how about a spider and then saw there was a demon there. Use that demon.

Let's look over here. and look at all our cards. We probably don't want this ghost and the pumpkin and the spider as literals, string literals in there.

It'd be kind of cool to have an array of them. Right? And then get the things out of an array. So how do we create an array in Swift?

Very important, obviously, to be able to create arrays. And we do it like this. Let emojis equal.

Now again, I'm inside this HStacks view builder, so I'm allowed to do variables, but they're lets. Even though I can use type inference, I'm still going to say this is an array of string. I'm going to set it equal to a literal array. This is how you do a literal array here, open square brackets, and then the things just listed in there.

Let's look at that type though first, array of string. Array is the array type. The angle bracket string, you're probably used to this from Java, it's because array is a generic type.

Array can have anything in it, an array of anything, but you have to say what it is that's in it when you create the array. So here I'm declaring that emojis, a little local variable here, is an array and there are strings in it. We are going to build our own generic type, believe it or not, in next lecture.

Doing these kind of generic types, really important, really fundamental part to doing Swift. So we're going to do it in our very next lecture, and that will demystify this. I know for a lot of people, a lot of students I talk to in Java, they don't really quite understand what's going on here.

They just accept it. Oh, yes, array, I have to parameterize what's in the array. Fine. But we're going to show next week exactly how this really works.

Now, this is an array literal, meaning the kind of. an actual array. And so I'm going to put these guys up inside here, make myself an array of these emoji strings.

And now that I have an array, I can use it to set each of these things. For example, I can make this guy be emojis sub zero. This is how you index into an array.

So you might imagine square brackets, emojis sub one. Make it a little easier on myself by copying and I'm pasting here. And if I tried to do emojis sub four, what would happen?

Crash, this is crashing my preview. That's what this means. Memorize quit unexpectedly over here, understandably. And you can see why, see where it says cannot preview in this file?

Index out of range. Clearly there are only four things in that array. So if you go for index four, that's past the end of the array. And that would happen in your app as well.

Array index out of bounds is a crasher in Swift. Don't do it. Now, this emojis var that I created here, it can live here inside this view builder, or it can also live up here at the top of this var as just a local variable inside that computed property.

And of course, it could also live up here as a var or a let, if you want to call it that. in our ContentView. So you can scope it where you want. And normally you would put things close to where they're used. That's just good programming style.

But something fundamental like this array of emojis, it's kind of driving our whole thing. I might want that at the top level. Array of string looks like that. I like to write it that way because it's so clear that my type is array where the type is string.

But there's another way to do this, and which is you're going to see a lot, which is open square bracket string. This is... exactly the same, identical.

It just looks different, but it means exactly the same thing. An array of string. You'll see this a lot. Again, I prefer this way, but you can do whichever way you want.

It seems to me that the world of Swift prefers this way. Because, for example, if we let Swift infer this, which we of course would, get rid of this and let it be inferred, if I option click on it, it uses that square bracket string. notation, you see. I think that's proof positive. That is the preferred way to do it.

I think the reason I like array of strings is because I'm teaching it to you and it's clearer to see what's happening. That's all. All right.

It's great. This is working. All right. Still working. Yes.

The things in the array got passed along there. Now let's address this problem that we have this repeated code. Four lines that look almost exactly the same. They're reaching into an array.

Clearly we want... a for loop here, right? We want a for loop.

So we can just go through the array one by one and make a card view for it. Unfortunately, inside a view builder like this HDAC, you can't do for. I told you you could do those three things, conditionals and the list and the local variables.

I'm not going to add for to the list for you. Sorry. In fact, all the things I've told you, that's all there is.

There's no more. So how the heck are we going to do this if we can't do a for loop in here? The way we do a for loop in here is with a special view. It's a special view called a for each. And it's a bag of Lego view, right?

The for each. view, got views inside of it, but they have to be passed to something like a VStack to lay them out on screen. So I'm going to create a for each view, a for each bag of Lego that's going to have four card views in it, just sitting in it, and the HStack is going to lay them out. So what does it look like to create a for each view?

Well, it's just a struct, struct that behaves like a view, it's called for each, has a lot of different kinds of arguments. Look at all these different things you can do down here, but I'm going to show you this. variant of it, which takes an array or any sequence, actually, of things. And I'm going to give it a sequence which is a range, 0, dot, dot, less than 4, which is Swift notation for a range of integers that goes from 0 up to, but not including, 4. And if I wanted up to and including 4, I would just change this little less than to be a dot. So three dots mean up to and including 4. So there are five things in that range.

there were four things in this range, right? Zero, one, two, three versus zero, one, two, three, four. So I give that. Now, the second argument here to for each, I'm just gonna type it and I'm gonna ask you to trust me.

Not gonna tell you what it is. We're going to go into it. Believe me, I'll explain what this is. But for now, if we're gonna do a range like this, you're gonna put this ID self, okay? Just, I hate to do this to you.

I know as you're like, oh, what? Please tell me, what is that? But I need to explain some other things first before I can really explain what this is. And I'll get to it next week. I'll get to it.

But we're not going to do it. Now, the foreach also has a view builder as an argument. This view builder is going to have the view for each of these four things. By the way, this is a range.

It could be an array. And then it would build a view for each of the things in the array, right? But here I'm going to do the index.

Now, normally this is not multiple. full views in a list, although it could be, right? You could be wanting to build eight views here, two each for each of the four things.

But usually the reason this is a view builder is you want the if-thens. That's mostly why this is a view builder. But essentially this for each is saying, give me the view you want for each of these things.

Now, since you're doing a view in here for each of these, you want this for each of them, right? Just like in a for loop, right? You need a control variable that tells you which one am I building right now.

And that actually comes as an argument right here, index in this. Here you have a ViewBuilder that actually has an argument to it. And that's perfectly fine because a ViewBuilder is a function. Functions can have arguments to them. This happens to be a ViewBuilder that takes an argument, index, right there.

And that index is set to 0. It asks you to make a view for 0. Then it's set to 1. You're asked to make a view for 1. You see what I'm saying? And the foreach keeps track of them. keeps track of which View goes with 0, which View goes with 1, which View goes with 2. So our View couldn't be simpler here. It's just CardView, CardView in here, and the Content instead of Emoji Sub-0, it's Emojis at that index, and we'll let the IsFaceUp just default for this. That's going to make four of these cards, and they're defaulting down here to FaceUpFalse.

I'm going to change that actually so we can See them a little easier. I'm gonna change that default to true. Now cards get created default face up.

There we go. And you can see the for each, it's a bag of Lego, and it has created four views, one for each of those things in that range. That's just as good as four, right?

So we're not missing not being able to do for loose because it's essentially doing the same thing here. And, you know, we're actually gonna find out that because this argument can be an array and because we're probably not gonna need this down the road. actually 4-H can be even more powerful than this and do some really cool things, especially when it comes to animation.

And the reason that's important for animation is if these are going to be moving around, you need to know which view goes with which thing. And that's what 4-H does. It keeps track which of 0, 1, 2, 3 goes with which of these views.

So if like the array gets reordered, it moves the views around in the bag of Lego and that caused the HStack to move them around or more likely a grid. It's kind of annoying that I'm doing 0 to 4 because I know there's four things in that array. What if I added something to the array? Am I going to have to change that 0 to 4?

I'm going to change this range right here by asking the array to give me a range of its indexes. So emojis is the array up there, and it has a little var called indexes, indices, and it returns a range that represents its indices. So we get all four cards.

and if i added something here like i made two demons then it's going to automatically give me five cards right the following range let's commit again so where's my source control commit and what we did here is we arrayified the cards with four each or something like that again we can look at what we did right we changed we added that emoji saying we put four each here instead of having four card views we made this default to false and we change this to content right so this is nice you can before you commit it you can make sure you didn't oh i didn't mean to do that you can also discard these changes you see this you can click on that thing in the middle and say oh no actually i didn't mean to do that undo that it's kind of cool feature so we'll commit that again i haven't submitted my homework yet i'm just keeping track of my changes let's go and talk about buttons uis want buttons you kind of looking at this and thinking, well, those cards are kind of like buttons. I'm tapping on them, and they're flipping over. It's kind of like a button. But what we're talking about here is buttons that the user perceives as a button in all apps.

In iOS apps, buttons are usually blue text. They look like blue text. No matter what app you're in, if you see some blue text, you think you can tap on it. And that's generally a button. You can have buttons that have other things.

We're going to build one with a blue image on it. So How do we put a button on screen? And I picked this because button is the most basic tool you have in your shed to start building your UI.

And we've actually done very sophisticated UI building so far. We've built custom views. These card views are custom built.

But now I'm going to show you one that's just built into the system. It's called button. I'm going to put this button in the H stack so that it sits right here. And what this button is going to do, it's going to add a card.

I need more cards than this. So I'm going to actually go up here. I don't usually like to do this, but I can't possibly type in all of those things. So I actually have a little code snippet here that's adding some more.

So if you're following along, I apologize. You're probably feverishly typing more emojis. But we need more emojis because I'm going to have this button make it so that it adds, and then I'll have another button that removes, so we can add and remove cards here.

Before we put the button, we need to have some infrastructure that lets us change how many cards we're showing. So I'm going to go away from emojis.indices here. We'll go back to a range, 0..less than, but I'm going to have a variable to be how many cards I'm showing.

So I'm going to have a var cardCount here. It's just going to be an int. We'll start it out at 4, our normal amount here. And then I'm going to add a button that increments that var.

I'm going to put it in the HStack for now. It doesn't really belong here, but just to keep things simple, I'm going to put it right there. Button, you say? It's a view, struct. It behaves like a view.

It has a lot of different variants for how to create it, as you can see right there. I'm going to use the one that takes a string, like addCard. And it also takes action, I think they call it, which is a closure.

I'm going to start right off the bat here and use trailing closure syntax for it. And this is any code you want to implement. This is like onTapGesture. This is the thing that happens when someone touches on it. And of course, I want cardCount plus equals one.

Oh no. We've seen this one before. Self is not mutable. We can't change card count. So how do we fix that?

I even heard people saying it, good job. At time state. Again, the number of cards that are up there, probably determined by our model.

We're not gonna need this at time state, but it's good for demos here. At time state's really good for demos. Not really used that much in real code, although you'll see there's some good reason for using it.

But anyway, we have our card count here. So now hopefully if I touch this add card, I get more cards. So look at that. It's working great. Super simple.

So buttons couldn't be simpler. Let's add one for removing cards too. Copy and paste. I told you every time I do copy and paste, I think, oh, how am I going to do this right? And we're going to see how we do this right before the end of this lecture.

We'll say remove card. And this is a minus equals one. And so now I've got remove cards. And.

add cards. But this is total garbage UI right here. We don't want these in the HStack.

We actually probably want them on the bottom, right? Maybe at the top, but I'm going to put them along the bottom. I want you to take two seconds and imagine how we would do that in your mind.

Can you picture that? Because it's really, really straightforward. We're just going to create a VStack that has this HStack of the cards in it and these buttons. The stack that has the cards and the H and then these guys down here.

This works still. I didn't break anything. I can still remove cards and add cards. But maybe I prefer to have these two things be side by side.

Remove card and add card. How would I do that? Again, I'll just throw an HStack right here. Stack those two buttons horizontally.

There we go. Remove card, add card. So you can see that putting these little HStacks and ViewStacks in it is super lightweight. You just throw these things in here.

Now, there's a couple of problems here with this down here. One is these don't look like buttons because they're orange. The buttons are not orange, they're like blue, or at least the accent color of the app. Why is that happening? Well, it's because this orange is being applied to the whole VStack that includes both HStacks.

So everything in there is getting turned to orange. And of course, we really want this foreground color orange to only be on this HStack up here. where our cards are.

And sure enough, they're orange. These things are blue. Another problem is removeCard, addCard. It's not, is this four different buttons or it's very hard for the user to see what is going on here. So I want like some space in the middle there.

How do I get space? Well of course there's a view for that. It's called spacer.

And put spacer in there and it uses up all the space in the middle. One thing I do want to Say this is not related to this, but we're here. Padding.

Someone was asking me last time, you know, we're putting this padding on this VStack. Does this padding get applied to everything inside the VStack, like foreground color? The answer is no, because padding is one of the view modifiers. Not many, but it's one that makes sense for the VStack itself to be padded.

Put padding around the whole thing. So there are some view modifiers that you give to one of VStack or an HStack that actually apply to the VStack itself. They don't get passed down in.

Padding is certainly one of them. And if you wanted padding passed down to each one of these, you'd actually do that with an argument to VStack called spacing. I showed this to you last time, right?

VStack parenthesis spacing five. That would put extra space or less space between them vertically. Everyone see how we're building our UI, putting things where we want with HStacks and VStacks?

Notice also that this way of building UI, completely independent of what device you're on. Like, look, I've got this. Why if I go here and say, well, let's make this be in landscape mode.

Here's my landscape. See, it's totally laid these out differently. Right? Even though this is in landscape, the cards aren't tall and thin.

They're a little shorter and fatter because landscape allows that, but it still spaced these things out to the edges. That's one of the real advantages of building this declarative UI like this. It's pretty much going to work on almost any device in any rotation. You might have to tune it because you don't want the cart to be tall and thin. We'll do some of that tuning soon.

Now, this button string do the action couldn't be simpler. But what if I wanted these to be images and not text? I want it to be a nice icon that represents adding and removing cards.

Well, to do that, I'm going to use a different kind of button creation arguments. I'm going to say button, and this time... the action comes first. So there's the closure for the action. And then there's another one that takes a closure, this time a view builder, which is the label.

This closure here is a view builder. This one, normal code, because this is the action I'm going to do, card count plus equals one. And the other one is a view builder that's going to be the views that are on the button.

And this is another thing about SwiftUI that's awesome, is that buttons could be any arbitrarily complex helicopter view if you wanted to, because the title of them or whatever you want to call it is a ViewBuilder. It can be anything we want in here. I'm going to have the action be cardCount minus equals 1. We're doing the remove one here.

And I'm going to have the label be an image system name. We already saw this with our globe. Remember we had the globe, and so this actually makes this... into a globe right here. I'll get rid of the text version of this.

I don't want it to be a globe though. I want this to be some other thing that seems like remove card. So how do I find that?

I'm going to go here and select globe. Let me do this plus up here. You see this plus in the upper right? This is the library. A lot of stuff in the library.

It's a great place where you're going to learn to go to look to find things. And here you can see it's already showing me. symbols. These are all various symbols and there are hundreds of them, maybe over a thousand, probably.

But you can also do other things in here like views. Here's the view, like here's button. Explains how to use it.

And if I double clicked here, it would actually put that snippet of code that makes a button in there for you. I wouldn't have had to type it myself. What else we got here?

We'll talk about those things later. Remember that code snippet I had there with the Halloween extra emojis? You can build your own in here. Images, colors.

You can have named colors if your app has particular colors. And then here are the images. Now...

You can type to search like we want to add something here. So I might type add and I'm looking down here. You can see there's lots and lots and lots and lots of things that mean add. And as I'm looking through here, I kind of see I see plus sign.

Yeah, that looks good too. Plus maybe there's things plus. And so I can kind of scroll through here and there's a lot of them trying to narrow down what I want.

By the way, you see that these have tags. Add, write, writing. This pencil one has these tags.

This, searching by this, I think is only in that latest Xcode. Otherwise, it's searching by the name. Pencil, tip, crop, circle, badge, plush, searching for those words. These extra tags, I think only 14.3 searches for those. We can pick anything we want here.

I kind of like this one at the bottom. You see that rectangle, stack, badge, plush, spill? That kind of looks like a plus for adding cards, doesn't it? A little bit. That's a good one.

Of course, that's plus, and I want minus. So we can look, see if we can find a minus version of that. Go up here and search for minus.

And, oh, there's the same thing, but with minus right there. Found what I want, double click on it, and it fills it in here as the system name. Long system name, it's very descriptive of what it actually does.

And, voila, look at that. You can hardly see it, but there it is. Now, it's too small, I want it bigger. We know how to do that.

You can of course say.imagescale.large. And if I wanted it really large, I could change its font to be, for example,.largetitle, which I'll do because this is a demo and I want you to be able to really see what's going on here. One might say, why is font applying to an image?

And the answer is images, these system images, try to track the fonts of the things they're near. Right? They try to work as inline. And so whatever font you set it to, it's going to make them match that. And then the image scale is relative to that.

Is it large compared to the size I would want to be for this font or small or medium? Notice also that we applied these to the button and it filtered all the way down into its label. That's the view modifiers, they always filter through.

We could have put this image scale font right on this image. That would be fine too. Also, we could put it on the whole age stack.

It probably makes sense because we want both of those buttons to get this. So let's put this out here. Put it on the HStack.

Now this is going to apply to both of these things. See the font got big. So let's do the same thing for the other one. Copy and paste. Yes, that means we have to do something about that.

Say plus equals one here instead of minus equals. And I'm going to use the plus version of here. I could go look it up again, but it's faster just to type it. Here I have plus and minus.

If I tap on these, more cards and fewer cards. And there's another problem besides this ugly code, which is that if I click this too many times, watch, minus, minus, minus. Whoa, my button moved out from under my finger, right? Because I'm tapping here. It moved up here because now there's no cards.

So it just went up to the center. That's not good. And if I hit minus again, oh, it crashed.

because I made the card count go down below zero. So now it's trying to do cardCount minus one. Let's protect our code against such nefarious use. For example, cardCount minus one, let's say if cardCount is greater than one, then we'll let the cardCount go down.

So we'll always have at least one card. And same thing down here. If the cardCount is less than the number of emojis, so count is a.

var, a computed var on array that tells you how many are in there, then we'll allow it. So now minus, minus, minus won't go past zero, and plus, plus, plus, plus, plus, plus, plus won't go past the number of cars, so we don't crash our app. Remember I told you 12 lines of code or less, please? I think I said you could do 20 if you push it.

This is way more than 12. And it's just really hard to read. If I look at that, I can't imagine how it's laid out. So let's talk about how we make our var bodies and all of our code kind of be broken down into pieces so that it's readable. Now, how do we do this? You could imagine.

that we could, for example, create more named views, like card view, helicopter view, right? Just maybe make a view for this and make a view for this, and that would help do it. But there's really no reason to do that.

Card view is a fundamental component of our UI. It deserves to be a view. It deserves to be a struct that behaves like a view.

This button, it's just a button. It doesn't need that treatment. So what's a lighter weight thing we can do to kind of encapsulate this thing and get some of the mess out of this var body up here, all these lines of code up here? Well, it's actually so simple.

Here's our var body. Right below the var body, I'm just going to create another var. I'm going to call it my card remover, and it's someView, just like var body is. I'm going to take this button out of here.

and put it in here. So now I've got this var, which is of type SomeView, it's a struct. Now I can just put it right here, cardRemover.

And I can do the exact same thing with this one. Take it out of here, create a nice var, cardAdder I'll call it, because that's what it is, it's a cardAdder. Put the space in there, go up here, and say cardAdder.

Well, already just that has made our var body much smaller and much more understandable about what's going on here. And I can take this another step further. You see this HStack of 4 each? That's my cards.

So why don't I take this out of here, cut that, and make something called cards, because it's my cards. var cards sumView. Paste that in there. Now I have a very understandable body. This body is a vertical stack of my cards.

with a horizontal stack of my remover and adder, and there's some scaling and fonting. But I think I can even do better than this, because these two things, the remover and adder, this pair is kind of like our card count adjusters. So I'm just going to put those in their own thing too.

I'm going to take this whole thing, cut this out, create a little bar for it that I'm going to call card count adjusters down here, put it right here, bar, card, count. adjusters, some view, there it is. Now, with bar-var-body, could not be simpler to understand what it is.

And these things down here are also each very simple to understand. They have a nice name, like cards or card count adjusters, and they're only a few lines of code each. Pretty cool with this? So we are definitely going to use this mechanism of creating these little sub-vars for little small things.

We wouldn't do it for our cardView. CardView is too important, but we would do it for buttons or things like that. Now this is great.

One thing I want to really touch on here, no one's asked me this, why doesn't this say return? This is a computed property here, right? So this is a function that returns this.

What's interesting is that this is not a ViewBuilder. This var card, some view, This is not a ViewBuilder. This is a normal, there's nothing here that makes it a ViewBuilder. Now what's inside the HStack is a ViewBuilder, but the HStack itself is not. So why didn't I have to say return there?

It doesn't make sense. It's not a ViewBuilder. In other words, this is not a ViewBuilder.

This is a ViewBuilder. This is not a ViewBuilder. This is just a normal function.

Well the reason for that is if you have a normal function and it only has one line of code, and this is one line of code, it's got a lot in it, but it's one line of code. then you don't need this return. It's called implicit return. And this works in functions and computer properties. If it's one line of code like this, is everyone okay with the fact that this is one line of code?

This is one line of code that creates an HStack and then modifies it. That's what this is. The fact that this argument to the HStack is a whole bunch of other code, it doesn't matter.

It's just an inline function, right? But the one line of code here is, is create this HStack, modify it. This is just an argument to the HStack. Okay.

I'm still not happy with this, however, because when I scroll down here, I see some code, this and this, which look almost identical, right? And I copied and pasted them to create them. And I told you, anytime I copy and paste, I always think, oh, is that really, how am I going to do this right?

I'm going to do this better than this. I'm going to create a function. that creates this button, a card adder or a card remover. And the reason I'm going to do that is I always want these to be the same.

So if I'm ever going to change anything about one, I want it to change the other one. So I want to have one function that builds both of them separately. So let's do that.

Let's create a little function up here. And it's also a great opportunity for me to show you what it looks like to make a function in Swift. It looks like this.

Func, just a keyword. The name of it, I'm making card count adjusters here. So I'm going to call this card count.

adjuster, that's the name of the func, and then it needs some arguments because I need to know whether I'm going plus plus plus right here or minus minus minus, and also they have different symbols. So I need an argument for where, which way I'm going and the symbol. So the argument for which way we're going I'm gonna say by offset, which is an int, and the symbol is a string. And this returns, anyone want to guess what this returns? No?

Yeah, someView. It returns someView. And whatever we put in here is going to return. It's just like var body or something.

And we're going to get Swift to look inside our function, see what we return, and that's going to be our return value. Now we've got to write this code. And this code is basically this button stuff right here. So let's start by putting this here and putting that here. And instead of having this, I'm going to have the card count here, the offset by this by offset thing, plus equals the offset.

I'm going to have the system name instead of being this particular thing be the symbol. So everyone see how this function works? Now let's talk about the by offset and the symbol arguments there. We know already that sometimes when we pass an argument like to a constructor of something, we have to say system name colon. We put like a label.

Same thing here with functions. Sometimes we have to put a label like symbol. So symbol right there is the name of that thing. It's a label.

When we have two labels, by offset, the first one is the label that callers use. The second one is the one we use inside our function. So you see, I used offset. By, I didn't even use the word by in here.

I used offset. But the person calling me is going to use by. This is external names and internal names for those little parameter labels, like system name.

Now symbol, since it's by itself right there, it's both the external name and the internal name. When you have just one word like that symbol, it's both. So both the caller and I, as the implementer here, am using symbol. So let's look at the call with that.

Let's have this card remover, instead of building the button itself, let's just have it return card count adjusters, adjuster by, this is remover, so minus one. And the symbol for this one is this with a minus. And again, I put return here because I want this computer property to return that. But this is one line of code, so I can implicit return there.

See? So that implicit returns even works if you're calling a function as the one line of code. Now, look how I called card count adjuster by minus one. I didn't say offset.

because I'm the caller of this function. And look how nicely this reads. Give me a card count adjuster by minus one using this symbol. And on the other side, up here, when I'm using it, I say adjust the card count by this offset.

I don't have to say card count plus equals by. That's just kind of weird. I want offset because that's what it is, the offset.

Same thing down here for this card adder. We're going to use the same function. We're going to implicitly return CardCountAdjuster by plus one.

In this case, the symbol is the same thing as before. And now we have our plus and minus are built by this function instead. And they still work just fine. Now, we've got a problem, though.

I lost my test to, you know, protect me against crashing. So we're going to do a little different way of doing that, which is I'm going to put a ViewModifier called disabled on there. And this ViewModifier disables the UI control under certain circumstances.

And the circumstances we want for our disabling here, that our card count plus the offset is less than one, or the card count plus the offset would be greater than emojis.count. If that's true, if either of those things is true, then we want this button to be disabled, right? So now if we go minus, minus, minus, minus, look, it disabled it, it's graded out.

Same thing, plus, plus, plus, plus, plus, all the way to the top. It disabled it. All right, now time is going to be tight here. The last thing I want to show you is to get rid of these tall, thin cards and put them in a grid instead.

So we do that really simply here. Instead of using an H stack for my cards, I'm going to use something called a lazy V grid. So a lazy V grid is something that takes a certain bag of Lego. Instead of laying them out in a stack, it lays them out in a grid with a certain number of columns.

Now, you wish you could say it like this, columns three. And I want three columns and as many rows as it takes. Unfortunately, you don't specify it with a number. You specify it with an array of grid items. So however many grid items you put in here is how many columns you're going to have.

Look at that, three columns. And if I add more cards. Watch, it just adds more rows. Perfect. Almost exactly what I want.

Unfortunately, look how it squinched my UI down. Why did it do that? Because lazy VGrid uses as little space as it can, whereas HStack uses as much space as it can.

It's just the way they're defined. That's no good. We need to put a spacer in here.

So I'm going to do that. That is a spacer between my cards and my card count adjusters. That's good.

Couple other small problems here. I don't have time, unfortunately, to explain why these are grid items, but there's a special kind of grid item, which is called an adaptive grid item, which takes a minimum size, like let's say 65. And the way this grid item works is it pours as many of the things in the bag of Lego as it can on one row and then goes to the next row. So just pouring them in there. minimum there is the minimum width. So if I said like minimum 120, you see they're wider and it's pouring them in there.

And if I create more, it just keeps pouring them in there. So this, I would normally explain this, but we're out of time. The last thing I want to show here is this problem.

If I click this, look what happens when I have a whole row where it's cards are faced down, it shrinks down. Why? Because a lazy V grid makes it as small as possible.

And when there's no emoji showing, those cards can be really, really small. There's no emoji there. So we're going to change our card view a little bit. Instead of using this if then to do this here, we're going to use opacity. We're going to have the front and the back always there.

But the back will be invisible when it's face up. And the front will be invisible when it's face down. So how do we do invisibility?

Simple. It's a view modifier. It's called opacity.

So let's do the back first. So here's my back. I'm going to say its opacity.

If it's face up, opacity is zero, which means fully transparent. And if it's face down, then it's fully opaque. So zero is fully transparent. And you can have anywhere in between.

It can be 0.5 or whatever. We want one or zero. Now, we have to make all of this so we can put opacity on every line.

Opacity, opacity, opacity. I'm going to show you another Lego holding bag of Lego thing called group. Group is kind of like a for each of one, if you want to think about it.

It's just a group to hold other Legos so that you can apply a view modifier to all. Group is a bag of Legos, so it can only be inside an HDAC or something. But it allows us to apply opacity is face up, question mark, one, colon, zero.

So the opposite way around. So look, it works. These cards are face up.

It's not showing the background because the opacity is zero. But when I click on it or tap on it. Now the face is fully transparent, can't be seen, and the back is fully opaque.

Now if I make a whole bunch and I click two in the same row, they don't shrink down because those emojis are still there. They're just transparent. Can't see them, but they are there. This mechanism of using opacity is a good way to switch between two states where you need sizing and spacing for all of them, for all the states.

You see? The problem with the if was that when we... had if it is face up being face up, then the background was just not even there and vice versa. When it was face down, the front wasn't even there.

It was not in the if was it was in the else. So the if wasn't even there. And so the emoji did not show up.

One last thing I know we're over time. These cards want to look more like this. These this is two across and three down, right aspect ratio.

One line of code to fix that we're gonna go back up here to our card view. Dot aspect ratio two by three. And there's another little argument here.

I don't have time to explain, but dot fit, which means fit it in the space available. Now our cards are two by three. The other problem is once I add too many cards, it knocks my buttons off the screen, so I can't do it. So we're going to go up here and put our cards in a scroll view. Scroll view is just a simple like a VStack-like thing, and it scrolls.

Watch this. Add more cards. Scrolling. Scrolling, incredibly easy.

I'm sorry. You just put a scroll view around it, and boom, things inside will scroll. Sorry I had to rush those last two. that's it i wanted to get that stuff in there because your assignment now oh let me sorry submit nice okay i'm going to commit right here here's my changes finished assignment one no not really finished required task one of assignment one still have more to go and now i'm going to go up here and say push okay push it up there i'm going to go up to github when i go up github i'm going to see two submissions up there, that initial commit I made on this one. Now I'm going to go off and do my homework, which is to get all this to this point and then to add some more stuff that shows me you understood everything I did here.

You won't have to do anything that I haven't showed you, just more of this kind of thing. And then you submit that and that's your assignment.