Stanford - Developing iOS 11 Apps with Swift - 8. Animation

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
[MUSIC] Standford University. >> Okay, well, welcome to lecture number 8, CS193P, Fall of 2017. Today it's all about animation, just going to continue a little bit of what we talked about Last time. There's three kinds of information I'm gonna talk about today. The first is UIView Property Animation. And it's exactly what it sounds like. You can animate these yellow properties on your view. This really the only things you can animate using this UIView Property Animation mechanism. But it's pretty powerfu. You've got the frame and center that animating the position of the view. You've got the bounds which will animate the size of the view, although only in a transient way because it's the frame that determines where you are. And so If you're going to make it larger, it occupies more space. In super view, you have to set the frame. The transform, which is super powerful, we saw that with the card, where we rotated the corners upside down. So you can do rotation, you could also do scaling with that, in fact, that's probably a better way to do scaling than trying to mock with your bounds. And then an opacity, another good one, fading views in and out, appearing and disappearing, and background color. We almost never do that, but you could do that. So the way this works is with a class called UIView Property Animator, not surprisingly. And it works with closures. And basically the way it works is you set a bunch of things on your property animator, UIView property animator, to say what kind of animation you want. How long it's gonna take, what curve of going through the process quickly or slowly it's gonna do. If you wanna delay before you start. All these kinds of things. You set that up and then you give it a closure. And inside the closure is just code that modifies these properties. So that's it, then it will animate those changes that you asked for in the fashion that you described. Okay so, this is a super simple way to animate these properties in a view. So let's take a look at what it looks like to call it now. I'm not even gonna scratch the surface of UIViewPropertyAnimator's power, it's amazing. You can create the se animations. You can scrub them backwards and forwards. You can have them with auto-repeat and reverse, do all these things. I'm just gonna talk about the simplest way to use it which is this class method running property animator. Okay, this thing creates a property animator that will immediately start running. Now the arguments to it are very simple. The first one with duration is just how long this animation is going to take to happen. So you can make it a one second animation, or a ten second, solely up to you. Delay is how long to wait before you start this animation. Why would you wanna delay the starting animation? Well, maybe you got some other animation going on you wanna wait, some time for definition although there's ways to chain animations too, so that when one finishes, you start another one, but it might delay for that reason. Then options we'll talk about later, just various options, how you want animations to run. And then the all important closure right there, animations, it takes no arguments, returns no arguments this closure and you just put the code that's gonna modify those properties in ther. And then there's a completion closure as well. This will get called when the animation actually finishes running. And that completion one has an argument there, which the position's either the start, because you might be running this animation backwards, believe it or not. Or it's the end, so the animation got all the way to the end. Or if the animation got interrupted in the middle, then the position will be called current, dot current. This is an enum right here, dot current. Why would an animation get interrupted in the middle? You start another animation that animates one of the same properties. Then that animation wins and it starts to taking over. Now you can have multiple of these property animators going side by side. All modifying different properties, but once one starts to pick on the properties of another one, the later one starts to win and take over. Okay, and then this one will stop, go to, say it's complete with the current position. Now, there's something very important to understand conceptually about how animations work. And I'm gonna show you an example of calling this running property on an, animator. That closure that you passed to it gets executed immediately. It does not take five seconds,or ten seconds, or however long for that closure to execute. It executes immediately and takes effect immediately. So, the animation is only what the user is seeing. The user is seeing your animation happen over five or ten seconds, but actually it happened the instant you started the animation. Okay, so there's a difference going on here. There's the reality, which is in your code. That all happens instantly, then there's the presentation of it that happens to the user. That's what happens over time. So, this can be confusing because you might have an animation that you set to take go off in two seconds, and it starts running for two seconds. And, you know you're thinking, yeah, my animation, when it's done, my frame or my center or my alpha will be changed. But no, the instant you started that animator, it got that stuff changed. So you have to think about these two timeframes. This is what makes animation somewhat difficult, is thinking about them, what we call the model, but has nothing to do with model view controller. But you know the actual reality in your code, and then the presentation which is a different thing if we're to user seeing. Alright, so here I'm going to take a view that's fully opaque and I'm gonna fade it out, and then when it's gone I'm going to remove it from superview. So this is basically make a view disappear from the screen animation. £So first, I'm just checking to make sure that I'm fully opaque, if my alpha is 1, then I'm calling the animator here. This animation is gonna take 3 seconds to fade out, and it's not going to start Until 2 seconds from now. That's what the first two arguments mean. Then I picked one option just for fun, which is allowUserInteraction which means that as it's fading out, gestures and stuff will still work on it. Otherwise, if you don't specify this then as animations happening this kind of animation, you won't be able to, you know, tap on things or whatever. Then here's my animations. My animation is just setting my transparency to zero fully transparent, right? So, that happens immediately when I call this method. This method running property animator return immediately having executed that closure immediately. And then I have a completion. If the thing gets to the end without being interrupted, in other words I do fade all the way out to 0, then I'm going to remove myself from my super view. That's what this little completion closure is all about. But notice I put print alpha equals whatever? That's gonna say alpha equals 0. Do you see why? Cuz even though it's gonna take five seconds for that alpha to go to zero it goes to zero immediately, because I executed this animation. So I've said that I'm sure your like yeah makes sense, but until you start coding it up you're gonna be like woah, that's right, I already changed that, it just hasn't appeared on screen, so you have to get a little bit used to that. What are some of the options that you can do when you're doing these animations? The first one, begin from current state, is if you're animating some property and then you start another animation that animates the same property. Does it start from the real value of the property, which is what it got set to, like alpha equals zero? So does it jump to transparent and start animating from there? Or does it pick up from wherever that other animation was? This is really, do you use the state of alpha that is being animated or do you use the real state of alpha which is what's in the code which using our previous slide, would be zero? Okay, so this is kinda like picking up or just using the real version. We do this quite a bit if we have overlapping animations that are doing the same property by two different animations. What else we got in there? We got repeat and autoreverse. So you can have animations that can go forwards and backwards and forwards and backwards. It's kind of in a loop. That's kind of fun. Down at the bottom you see these curves. Curve ease in, ease out. For example, third from the bottom. That's just saying that when you move the thing, does it move linearly? That's curve linear, which would be like it would move like that, or does it kind of ease into moving slowly then pick up speed and then slow down at the end? Now why would you want it to do that? Well things that move like very linearly feel kind of robotic and mechanical. Things that ease in and then ease out, feel a little more like someone picked them up and moved them over and put them down. You see. So it is just kind of more natural movement. So like for moving things you almost always want curve ease in, ease out. But other ones like maybe fading out to alpha. Maybe you don't need that you can just linearly fade out. So that's it for view animation, super, super easy. You can only animate those properties. It's super easy to do. And again, I only scratched the surface. I showed you one method in UIViewPropertyAnimator. It has dozens of methods, lots of capability in there. All right, so let's talk about a totally different kinda animation now. This is animating an entire view change. So you've got a view and it's gonna completely change the way it looks and you wanna animate that in some ways. And there's limited ways to do that. The classic example of this is a playing card. When a playing card is face down, it looks like the Stanford logo or it's the back of a card. When it's face up it's got pips and corners and all of that stuff. Okay, well when I wanna flip my card over I could just change it immediately and it would just change back and forth starkly, but this allows me to actually flip it over. Like a 3D looking flip. And you can also cross dissolve from one thing to another. Those are the two major things you can do here. There's curl up from the bottom too, which makes it like it's curling up, and you're looking at a piece of paper behind it and that's the new version. But that's only for views that fill the whole screen. Okay, that's not for, it doesn't really work if it's a view inside another view. It kind of doesn't feel right. So this is for transitioning just smoothing out or animating the complete change to a view. Play cards being the classic example. Here's how you would call it. You would use this method in UIView. It's a class method called transition with View. And you give again, duration. You give the option. Same options as we had before. Also with some options like transition flip from left, which means flip this thing from the left edge. You can also have flip from top, flip from bottom, cross dissolve, those kind of things. And now, in the animation block, you're not limited to just changing view properties, you can change anything you want, that's going to make that thing look different. And then, so what the animation system does is, it draws your view before this closure. Then it executes closure, draws the view after, and then it just flips it over or cross dissolves it, see what's going on here? So this is kind of a simple animator for big changes. Playing card is a great example and we'll see that in the demo that I'm going to do. All right, so that's number two. Here's number three. This one's a little more powerful, a little more complicated. Dynamic animation, it's a little approach to animation. Here we're gonna set up physics attributes on our views and then just tell them to go do what they do. So we're talking physics like density, friction, gravity. Stuff like that. You put on them there and then they just start moving because physics, it works. So let's take a look at how you make this happen. The first, there's three steps to making it happen. First, you need an animator. This is just the thing that drives the animation. It's an instance of a class called UIDynamicAnimator, it only takes one argument in it's initializer, which is the view that is going to be the reference coordinate system for all animation that's going on. And the only requirement for this view, can be any view in your app. It just has to be a super view, or a super view of a super view, or a super view of a super view. It has to be at the top of the view hierarchy of all the views that the animator is going to animate. But those views don't all have to be in the same view. They could be in sub-views of other views. As long as they all are eventually have this view. The reference view as a super view. So a lot of times people wanna make their whole view controller's view, and that's what I'm gonna do in my demo. Be a reference view cuz I don't have any other views. But like in your assignment you might just make your view that contains your cards be your reference view. Because you're only gonna animate the cards. So you don't need to go higher level, all right? And in fact, it's better to stay lower down, because it can be more object oriented to have that animation code down where it's actually happening instead of putting everything up in your controller. But I will say by the way, especially when you see me do demos, which are really lightweight, there is a tendency you probably have as beginning iOS people to put everything in your view controller when maybe it wants to be down a level in a custom view. So just food for thought there. Think about what level things wanna be at. All right, so number one is to create the animator, number two is to create behaviors. So this is describing how the things in this view behave. And so we're talking about gravity, and whether things collide with each other, stuff like that. Okay, those are behaviors, and you add the behaviors to the animator, so animator has an add behavior method. You call it after creating a behavior and you just add it. Now as soon as you add that behavior. That animator would start enforcing that behavior, whatever it is gravity or collisions or whatever. But there is no items yet that are being affected by the behaviors. So step three is to add items to the behaviors. So it's creating an animator, add behaviors to the animator. Now add items to the behavior. The instant you add an item to a behavior, it will start being affected by that behavior assuming it's in an animator. And UIView's are the items here, but actually there's not UIView's it's any object that implements the protocol UIDynamicItem, which doesn't have to be a view. I'm gonna show you that protocol in a second. But UIViews are 99% of the time what we pass items to these behaviors. So if I say gravity add item, item 1, item 1 will immediately start feeling the pull of gravity. If I say collider at item 1 immediately item 1 will start colliding with everything else that is added as an item in collider, in the collider. You don't say go. It's just like as soon as you add an item it starts being effected by that behavior. And if you remove an item it instantly stops being effected by that behavior. here's that UI dynamic item protocol that view implements. Okay, so View actually implemented three of them automatically. It already had a bound, center, and transform. Okay so it automatically implemented those. And then the other two are just really simple things having to do with collisions. I'm not even gonna talk about those. But you can see by what's in the dynamic item protocol what the animation system is Is able to animate. It's able to animate the size through the bounds. It's able to animate the position through the center. And it's able to animate the transform, so it can rotate and scale and do anything it wants pretty much in that front. And it tends to mostly use transform in center to do its animation. Okay, the bound to be notice is read only. So the bounds is just for the views that can obviously look at their own bound from their drawing and stuff. The animator doesn't actually change the bounds, as it goes. Mostly doing center and transform. By the way, if you give an object to the animator by making a behavior operate on it, really animator kind of owns it. It owns a center and transform as it's going to change them around. So if you wanted to change a standard, or transform off a view you already gave to a behavior, then you have to call this method in your dynamic animator which is update the item to the current state. In other words, I changed the center or the transform, please, Mr. Animator, take that. And so the animator would take that state, move the object, change its transform, and then start behaving on it again. Keep going forward, behaving from there. All right, so let's talk about some of the behaviors you can have. I mentioned gravity. Gravity is an easy one. By default, gravity is down, meaning down towards the home button or the bottom of the device. Like an iPhone 10, it's away from the face recognition stuff at the top. And the magnitude, we sometimes call it G, but it's not really G. The magnitude of 1.0 is 1,000 points per second per second. Everyone knows what gravity is, right? It's an acceleration, 9.8 meters per second squared. So, this is 1,000 points per second per second. Now, what's incredible is 1,000 points per second per second feels a lot like 9.8 meters per second squared. In other words, if I put an object at the top of my screen and added to it a behavior with that gravity, it falls at about the same rate as a real life object would fall. It's amazing that, that round number ends up being like that. But it is. And then also, gravity doesn't have to be down. You can make gravity go up. Or off to the right or anywhere you want. So you can completely control what is going on with gravity. Attachment behavior, really cool one. Think of it as like an iron bar, or a bar between either two items or between an item and a fixed point, all right? And that bar keeps those two items connected even as all the other behaviors work on it. So, imagine you had two things connected with a detachment behavior. So they're behaving as if they're attached to each other. And they start to fall because they're both being operated on by gravity. Then let's say one of them collides with something. That bark is gonna make it, so the other one which didn't collide is gonna swing like a pendulum, you see? Cuz this one came down, it collided, and this one kept on going. But the bar keeps them together. And same thing if you attached it to a fixed point. And then you turn on gravity, it will start to fall down. But then when it gets to the bottom, it'll start swinging like a pendulum. And then eventually, gravity will pull it so that it's straight down. You see what I'm saying there? So attachments, awesome, cool little things. Another thing that's cool about them is you can change the length of this bar while the animation's happening. So it's animating. Things are falling, bouncing off, things are colliding with things. They're attached, and you can make the bar get closer together or farther apart. You can also make the bar kind of springy with a certain amount of damping. So that things hit something, and then it will come together, and then come back up to make their attachment be the right distance. It's pretty cool behavior. Then there's collision behavior. This is probably the most common behavior. Okay, this is objects UIView bouncing into each other or bouncing off bezier paths, basically, in the background. And you set this up just by adding any fix boundaries you want as bezier paths, usually, and then adding items. And you can control whether the items bounce off each other or only off these boundaries as bezier paths as you set up. All these bezier paths are in the reference views coordinate system, by the way. They're not actually drawn or anything. They're just conceptually boundaries in the space. And there's a really cool var in there. The second from the bottom there translates reference bounds into boundary. If you set that to true on a collision behavior, then your reference views outer edges will become a boundary, which is common cuz you got things bouncing around in your reference view. They'll stay mostly inside. Now, one thing about collision boundaries, a lot of people think, if I put my reference bounds as collision boundary, then no object will ever get out. Okay, it will never escape. But that's not true. Because items can, for example, move so fast that in one animation frame, they move from being on one side of the boundary to the other. And then they just fly off in the outer space, they're gone forever. So collision boundaries, they only check for collisions on each frame of the animation, and so they're not a lock down guarantee. You keep things inside if you put a collision boundary around everything. Now, collision boundaries also have a delegate. Their delegate allows you to find out when collisions happen. So if you set something to be a collision delegate, you'll get these methods like collision behavior, began, contact for some dynamic item with a boundary and an identifier with a name. By the way, notice the boundary identifiers are of type NSCopying. That's really weird old Objective-C thing. That just means that they're either an NS number or an NS string. And so you can use S to turn them into a string or a double or an int, because we know that NS number and NS string can be like automatically as to their Swift counterparts there. Collisions are cool. SnapBehavior, also very common. This is when you're using the dynamic animation system, how you move something. So you wanna move something someplace else. You're not doing view property animation here, you're using dynamic animation, so you say snap to this point. To little better than the view property 1, because when it snaps there, it doesn't just jump right there or even ease out and ease in there. It actually gets there, and when it's there, imagine it has four springs on the corner. So it kind of comes in and vibrates a little. So it feels even more natural flying across the screen and stopping. So you'll probably use the snap behavior in your homework because you have to throw matched cards into a discard pile. You're probably gonna use snapTo to throw them out there, cuz you're gonna be doing dynamic animation with them otherwise. Then there's PushBehavior. Okay, PushBehavior just pushes an object. And it can either constantly push it. Or it can just push it once, like punch it. And this is an interesting one. You can specify the angle and the magnitude of the push. The instantaneous one is kind of interesting, because think about it. This is a behavior that's added to an animator, and this behavior only fires once if it's instantaneous. And then it's just sitting there doing nothing forever. So it's just kinda croft. So it'd be nice if there was a way that we could add a PushBehavior and say, after you fired, please delete yourself because you've already done your work. And I'm gonna show you how to do that, but it's gonna require me to teach you a little more about closures, which I'll do in a little bit. Another one is a UIDynamicItemBehavior. This is like a meta behavior. So this is a behavior where you specify things like friction and elasticity and whether you allow rotation of the view as it's bouncing off of things. And this affects how all the other behaviors work, right? If you add more friction, then obviously, gravity pulls on things. They move slower, because they have friction, etc. You can also ask the UIDynamicItemBehavior about all of its items. Things like, what's your current velocity. How fast are you moving across the screen, or even how fast are you spinning if you happen to be spinning, how fast is that happening? So UIDynamicItemBehavior, we almost always have all or items in a UIDynamicItemBehavior, because we wanna be able to set these various things about them. Then there's UIDynamicBehavior, which is the super class of all of these behaviors, collision behavior, gravity behavior, all of these things. Now, you could subclass this and try and write your own behavior, but writing a gravity behavior is pretty hard. A lot of math involved there, trying to get that to work. But that's not why you use a subclass of UIDynamicBehavior. What you do with UIDynamicBehavior is you collect other behaviors, like collisions and gravity and all this other stuff, into one behavior. So that you have one behavior that you add your items to, and it's got all these children behaviors that are making it behave in all this way. We're gonna do some of the demo as well, and the way you do this is you call addChildBehavior on yourself if you're UIDynamicBehavior sublass. And it now causes that behavior to be your behavior. So very simple. UIDynamicBehavior also has a bar in it called dynamic animator. That is the animator you are currently being animated by, if any. Okay, so you can look at this to find out. Am I currently being animated? This behavior or is it not being animated? And if it is being animated, who by? And it will even send you a message. Will move to animator when you switch to a different animator. Usually when you go from not being animated to being animated or vice versa, all right? Now UIDynamicBehavior has another awesome var that you inherit when you create a subclass or when the subclasses are created. Which is action. So action is a closure. Takes no arguments, returns no arguments. This closure gets executed every time this UIDynamicBehavior Acts. So like a push behavior that's instantaneous, this gets called once. Cuz only once does it act on object. But a collision behavior is kind of always acting on the outer gravity behavior always. So it's this thing is getting called a lot. So never put any code in there that takes a long time to execute because it will slow your animation right way down. Cuz these things are being called all the time, these action closures right here. But they can be kind of useful. For example, you might wanna check in here how's my view left the building, right? You could look at whatever the items of this behavior's acting on and did it cause this thing to go outside the reference bounds even? Or whatever, so that I have to put it back or maybe destroy it, or something like that. So again, it only takes a couple lines of code to check the bounds of your item against the reference bounds and see if anything has happened. So that would be okay to put in your action. Now we're gonna use this action thing in a second to fix that push behavior problem to, all right? Finally, I'm gonna talk about stasis of the animator. Really, most of the time we design these animation mechanisms, with all of the gravity and collision boundaries and physics and all that stuff, we design it in a way that we expect it to come to a stop. Okay, and then maybe a push happens, and it goes back active again. And then it eventually comes to a stop. Most of the time, it's not required. You could have things just constantly going around all the time. But usually you push, whatever comes to a stop. And so you can find out when it reaches stasis with the UIDynamicAnimator's delegate which has these two methods. Did pause and will resume. And it will tell you, I came to stasis. I'm not currently moving anything and then something happens. It'll say I'm resuming because I have to move things. Okay, all right? Let's go back and talk about that push behavior thing. So what I really wanted to do something like thing. I got this push behavior, right? I set it's magnitude and its angle. But I really want that push behavior to get thrown away as soon as it pushes because it's instantaneous. You see here, my thing here is instantaneous. Push, so it's only going to push once. And then I want it to get thrown away, so it doesn't muck up the heap with a totally useless behavior, all right? Well, I can do that with this action method, the yellow part right here. So I can just have the push behaviors action all behaviors inherit this action closure. And inside the closure I can tell, ask the push behaviors animator, please remove me. Okay, remove behavior. The push behavior itself. So that's cool, right? It solves the problem. Well yeah, but red on the slides. This creates a memory cycle. So we're gonna talk a little bit about this memory cycle that this creates. And the bottom line here is that the push behavior is pointing to that closure. That closure is pointing back to the push behavior. So both of them have a pointer to something them in the heap. And so they're both being kept in the heap. All right, so for me to tell you how we're gonna fix that memory cycle, I'm gonna have to take an aside here and go back and teach you a little more about closures. Hopefully, you've read about this in your homework assignment. But here we go. You can define. When you have a closure you can define some local variables. Fun little local variables. You put a little square brackets with these local brackets in there right before you're in with your closure and you can define any variables you want. And you can set them to have some initial value. So here I have variables x and y. And I can use x and y inside my closure. And then I'll have whatever value I set there. It doesn't seem like it's of very much use. And you're right it's not because, of course, I could use some instance of a class, or I could just type hello inside my closure. Why do I need to have the silly little variables there? Well the reason they're not so silly is because they can be declared weak. These x and y are different variables. They're local variables just for the closure. And if you were to declare one of them weak, then it now becomes an optional, first of all. Because we know all weak variables, they don't keep things in the heap. And they also have to be weak because if the thing they're pointing to goes out of the heap they get set to nil, right? So I can now use x and y still inside there but x is now optional and weak. Okay, and you're gonna see why that's valuable in a second. I can even declare that those variables be unowned. And if you remember back to the thing when I was talking about memory management, unowned means it's not reference counted. It's like Swift says okay, it's on you. If you are gonna access that thing, it better be in the heap because if it's not, you're crashing. That's basically what unowned means. So that means I can use x and y inside the closure. It's not an optional, don't have to worry about that. But if I use x in there and it's not in the heap, it's gonna crash my app. Okay, so why is it valuable to do weak and unowned? Well because weak and unowned variables, whether they're these little locals or not, don't keep other things in the heap. They're not strong. They're weak or even unowned. So we can use that to break these memory cycles. So here's another example of a memory cycle that happens all the time. Have a class here, Zerg, it's called. It has a var called foo which whose value is a closure. Inside that closure, I've set it to have a value of a closure that calls another function in Zerg, which is bar. This is a memory cycle. And this is a memory cycle because foo, the closure, is repressing self. Self is keeping the class Zerg, the instance of the class Zerg, in memory. And of course, Zerg is keeping the closure in memory cuz it has a var that points to it. So they're pointing to each other and keeping each other in memory. So that's bad. You got one pointing to the other, the other points to the other, so how are we going to break this cycle? Well, we're gonna use those little local variable hoo-has. So I'm gonna create a little local variable. It's weak. I'm gonna call it weakSelf. I'm gonna say equal to Self. And then inside, I'm gonna say weakSelf, question mark, meaning optional chaining, .bar. So now there's nothing inside that closure, no variable in that closure is keeping a strong pointer to self. And so it's not keeping itself in the heap. Do you agree? Right, the only thing that's even used inside that closure is weakSelf, but we know that weak. Can't keep something in the heap. Now, unfortunately, it's an optional, so I have to optional chain, but I've broken the cycle. So this is how we break these cycles using weak little local variables in our closure. By the way, we are allowed to call that weak self, self. In other words, it can have exactly the same name as a variable in the outer scope. So on this slide, there's two different selfs. There's the yellow self. That's a local in the closure. And there's the green self. That's the more global self of the instance of this class. Okay, so it's two different variables. But I'm not using the green self inside the closure at all. I'm only using the yellow self. And that one's weak. And in fact, this is so common you don't even have to say equal self. If you say weak self, it will automatically set it equal to a variable of the same name in the surrounding code. Okay, so this is how we break these cycles. So now let's go back to UI push behavior and look how we would use unowned. All right, so here's my push behavior code. If I take that same thing, that action, and just say unowned push behavior in that, I've broken this cycle. Because now pushBehavior is no longer managed by the reference accounting system and, but pushBehavior better be in the heap right here. Okay, if it does it in the heap, we're gonna crash. But of course, pushBehavior is gonna be in the heap there. pushBehavior couldn't possibly even be executing here. It's action could not be executed if it wasn't in the heap. So we're guaranteed that that pushBehavior's gonna be there. So this is a classic one where we would do unowned to break the cycle. We could've also done week there and maybe checked to see if it was nil. But it's all unnecessary cuz we know that's never gonna be nil there. It's never gonna not be in the heap.So now I'm going to do a demo. We're gonna take the playing card thing we did last week. And I'm gonna make lots of playing cards. And we're gonna put them in motion, and have them flipping over, and all kinds of fun stuff. Friday, I'm not gonna get back to the slide. So let me tell you that Friday, we will have a Friday section. It's on source code control. So don't miss that one, that's a good one. That's the thing we're always not clicking when we create new projects that say, do you wanna manage this? Connecting your code to GitHub and all that stuff is part of that. Next week, actually, I say these, but actually, we'll definitely do view controller lifecycle. But I'd probably gonna do multi-threading next week, which is an important topic. And then maybe some miscellaneous other UI things, like scroll view, text field, things like that. Probably do table view and collection view, and drag and drop, even, the week after that. All right, so I told you I would show you the homework animation thing, so let me do that. Let's go do it here, where is it? Okay, so again, I'm only gonna show you this briefly. But I just wanted to give you an idea of what you're gonna do. Your set app, when it comes up, is going to automatically deal out the cards. You see how I'm dealing out the cards? They're flying out, and they're also flipping when they get there. When I deal new cards, you see, it's smoothly animated to smaller cards, you see? And then dealt out new cards, so you're gonna have to do that. Notice, I only have diamonds. That's cuz I wanna get closer to the end game faster. This is debugging thing. Also, any three cards are a set right here. So we do this. Now see what happens when you get a set? They go chaos, they just fly all over the place. But then they eventually collect over here in my discard pile. See, look, watch this again. See them flying all over the place? And notice, it's also dealing out new cards to replace them. Eventually, I'll get towards the end here. There won't be any new cards. Notice that my deck nicely disappeared. That's not a required task, but you probably wanna do that. And then, of course, as my number gets smaller, you're still doing the chaos animation. And we're rearranging the cards as they go down. Understand what we're doing? So you're basically doing three animations there. One is you're dealing the cards out and flipping them. Another one is, when cards match, you're exploding them. And then putting them in the discard pile. And then every time the number of cards changes, you're smoothly animating to the new number. You're not jumping them. That's it, all right, that's it. So the demo for today, which will hopefully help you, let's go here. All right, so the demo that we're gonna do today, which will help you with your homework, is to take our card app and put these cards in motion. Now this is pretty much the same exact card app we had last time. Except that the difference is we used to have one card in the middle. In this case, I've created 12 cards. And I've used my viewDidLoad to make them be random cards. I'm just randomly going through, creating these cards, face up, putting the rank and suit on them to create random cards. So let's see what this looks like. All right, there's my random cards. They don't do anything. I don't have any gestures on them. So I can't tap on them or do anything. So the first thing I wanna do is add a tap gesture that flips them over. And we're gonna start this app and flip them over without animation. Then we'll add animation to have them flip, all right? So let's do that. I also heard there was some confusion in office hours about gestures. So this'll be a good opportunity to review gestures again. So what I'm gonna do here, first of all, I'm gonna start my cards face down instead of face up. And then I'm just gonna add a gesture recognizer to them all. add, let's make this code big so you can really see what's going on here, right? So I'm gonna addGestureRecognizer, oops, gotta add it to a view. So I'm gonna add my cardView.addGestureRecognizer, hello. How come, there we go. So I'm adding a gesture recognizer. The gesture recognizer I'm gonna add is UITapGestureRecognizer. And then just to review what the arguments are to a gesture recognizer, there's the target. This is the object that's going to be sent this action method when the gesture happens. So I'm gonna have the target of this be myself, the view controller. But it could be the view, I could send it to that view as well. And this selector is just #selector. And then inside here, you put the name of the method. But only the external names of the arguments. So, for example, I'm gonna do flipCard. And it's only gonna have one argument, which is the tap gesture. And I'm gonna have it have no external name, so I'm gonna do_:. So this is the name of the method with all the external names and colons in there, but nothing else. That's all, everybody cool with that? Now, This is just complaining cuz I haven't defined flipCard. So now when I create flipCard, of course, it has to be objc. Okay, all these things that do this action thing have to be objc. Call it flipCard, has no external name. It's gonna be the recognizer UITapGestureRecognizer. Recognizer, sometimes it makes me type it all in. See if that gets rid of our warning here. Okay, what's it saying here? What do we, the objc func, how about that? It's rebuilding here. Okay, good, that's fixed, are we over here? Did I forget something here? Okay, good, code's a little slow to keep up with my typing today. Okay, so now I have this recognizer. Now I want to just find out which card was clicked on, and then flip it over. And this is something that I think people are like, how do I do that? And the answer here, of course, I'm gonna switch on the recognizer.state. We almost always switch on the state. That's the first thing we do. It's a tap gesture, so I only care about the ended state. All the other states, I'm just gonna break, right? But if I get a tap, what I wanna do is get the card that was tapped on. But I'm in my view controller. Now normally, I might have the target have been the view so it would know that it was itself that got tapped on. But here, I'm in the view controller, so I have to find it. Well, it turns out, TapGestureRecognizer, it knows what view it was tapped on. Remember how I said each of the concrete gestures have information about what happened? Well, one of the things is they know what they were tapped on or whatever. So I can say, for example, if let the chosenCardView = the recognizer's view. So view is a var in TapGestureRecognizer, which is the view that was tapped on. This is maybe the piece some of you didn't get from the doc. As a PlayingCardView. Now why do I need it as PlayingCardView? Because this is just a UI view, of course. TapGestureRecognizer doesn't know anything about PlayingCardView. So I have to make sure it is, in fact, a PlayingCardView. It should be because that's the only thing that I added any of these tap gestures to. Then I can take the chosenCardView here, and have it isFaceUp. =not the chosenCard view isFaceUp. So I'm just flipping the card over here, right? Everybody understand that? Okay, so let's go ahead and run and see if this works. Okay, so they're all face down, that's good. See I changed them all here to be false. So let's click, all right. Flipped it over, working nicely, okay. But of course we want this to be animated. And we know that that is really easy to do with that UI view transition with a view thing. So how do we do that, how do we get started on that? Let's just go right here and surround this and put this in a closure that's in a UIview transition, so UIView.trasition. By the way be careful, their is two different UIView transitions. There is with right here, which is the one we want and there is from to. The from to is when you're transitioning from one view to a completely different view. And you're going to remove one from the superview and add the other one in as a subview. So it's a similar thing, but it's like card where the back of the card goes one view and the front of the card is a different view. But here we're transitioning with the same view, cuz we can turn it face up and face down. Here I'll go ahead and spread this out so you can see these arguments a little more clearly, right. Okay, the arguments, it seems like there's a lot of arguments here but they're all quite simple. So the view we're flipping is the chosenCardView of course, all right? The duration, I find like a half a second, maybe six-tenths of a second is a good amount of time to give it to flip over. Any slower and it feels kind of glacial. Any faster and it's like, what happened there? It kind of flashes at you, so you know. Options, here's where we have to say that we want to flip as opposed to cross dissolve or anything. So I'm gonna make transition, flip from, let's flip from the left. So we gonna flip from the left. Here's the animation's closure, okay this is where we're going to do whatever cause is the view to look totally difference. So, in our case it's this code right here, put this inside here. Don't need that way over there. But and then completion, for now we're not gonna do anything when it completes. So I'm just gonna take the completion thing completely out of there. And we'll put this down here, whoops, Ctrl+I just to make things a bit neater there. Okay, everyone got that? So here's our transition. With this view, flip from left, we're just going to flip the card over. Let's give it a try. Okay, here we go. Woo, see, and look, see, how it kind of shades a little bit. The shading, the kind of gray that gives it that 3D effect. It feels a little more like a 3D flip. And it flips it over. Okay, so that was easy. So that's transition animation, totally trivial. Let's we're not really going to make our app here into a concentration app. We're kinda doing a little bit of a UI study here, but what if we wanted this to be like concentration where we're trying to find two matching cards? Okay, but we're going to make ours a little bit harder. We're going to make it so that when you flip over a card, so here is this card flipped out. If I flip another card, if they don't match it's gonna go like this, flip and then immediately flip them back down. Hm, so you really are going to have to concentrate. Okay, this really is concentration cause they go clip, and then flip, didn't match. You see? So we're gonna have to really watch what we're doing here. So let's add that animation, that just turns cards down. Any time there's two cards up, we're gonna turn them down. So to do that, I need a little private var that tells me what my faceUpCardViews are. Which is gonna be an array of PlayingCardViews here. And I'm going to have it be computed. And we know how to use filters and all that stuff, so I'm going to have my face up cards be my cardViews, return my cardVews filtered and the filter is that they are face up, right? Let's also make sure that they're not hidden. Because I'm gonna be hiding cards that are already matched a little bit later. So I just don't forget we'll make that be hidden. So now we're able to find out what our face up cards is. So now what I'm going to do is after this flip over finishes, I'm going to add a completion handler. Okay, completion, which I didn't have before, I'm adding it. This one has finished. It doesn't have the position, end, start, current. It just has finished, yes or no, whether this flip finished. Because, it doesn't really make sense to move your flip backwards and forwards. It kind of either happens or it doesn't. So inside here, I'm gonna do another transition. Okay so actually I'll just copy and paste why not. Copy, paste and it's perfectly fine to have animations in the completion handlers of other animations. That's perfectly fine. So what am I gonna do here? Here I'm gonna flip down all the face up cards if there's two. So let's go ahead and check, if my faceUpCardViews.count == 2, so I've got 2 face up cards. Then I'm gonna flip them all face down using another transition. So let me show you a cool way to do for loops if you have an array. Watch this, and see here, what it's saying? Let's stop and look at this. I got an error here, it says Reference property in closure requires explicit self to make capture semantics explicit. Okay, this is awesome. Swift if saying, wait a second here bud, you are accessing a bar on yourself, and that's gonna capture yourself. And I want you to type, self., right there, so that you realize that you might have a loop here, a memory cycle. So isn't that cool Swift made us do that. I really appreciate that Swift does that. Cuz otherwise it could be really easy to forget. Oops, this is self and then realize, I got a memory cycle. But do we actually have a memory cycle here? No, we don't, because while this closure does capture self. Self does not in any way point to this closure. It's not part of any var. It's not any part of a dictionary or an array or anything that self has. It's a closure we're giving off to the animation system. So only the animation system has a pointer to it. So there's no cycle here. So there's no reason for us to do any of those weird local variables that are weak or any of that. All right so, if I have two face cards, I'm gonna do a for loop of all the face cards, but I'm going to do it like this. Watch this, Face up cards, forEach, do a closure. So, I'm going to execute a closure for each of these face up cards. So, that is kinda a cool way to do for loop, and inside there I'm gonna put this here. And of course in the for each $0 is each of the things in this so I'm gonna do this flip transition with each of the cards in here. Same thing same thing. Here's it's $0 is face up and it's not flipped over, it's false. We want them face down. You got all that? Make sense? What do we got here? Let's get rid of this right here also. And what's happening here? $0 is face up. Okay, anyone see what I'm doing wrong there? Look at my card here, cardView, maybe I need to do this, cardView in. Oops, cardView or something like that. That fix it, yeah that did, that's interesting. So the $0, I couldn't get it to type right there. I don't know why it couldn't infer that Here, if we have two cards face up we will flip them both face down. Let's see if that works. All right so, one card face up. We can actually turn this card back face down, by the way. Face up, here we go, second card. All right, we're good. Excellent. Now what happens if two of these cards match like that? These two cards, they match, so it shouldn't do that, it shouldn't turn it down. It needs to give us some animation that says woohoo you matched two cards. Okay, so let's come up with that, an animation to do that. So the animation I'm gonna do when cards match is I'm gonna make the cards really big to emphasize that you have a match, then I'm gonna make them really small and have them fade away to nothingness and disappear. Because when cards match, of course I wanna take them away. So that's what we're gonna do. Now that's a two step animation. The first step is to make them big and then the second step is to animation down. And we can do that with the UIView Property Animator, of course, because of the size of the view and it's transparency are animateable properties. And to make it two step like that, we'll use the completion thing, similar to how we did this, right. We waited till the first one finished and then we went to do the other one. All right, so how are we gonna do that? Well, I need another private var here, actually. That's just gonna tell me whether the face up cards match. So I'm gonna call that faceUpCardViewsMatch, I think I called that. Is that what I called it? Yes, faceUpCardViewsMatch. It's a bool, we're going to calculate that as well, I'm just going to return, if our faceupcardviews.count=2, and oops. Kind of made it on the fly. And face up, I'm missing an A there, faceUpCardViews [0].rank=FaceUp, and you can kind of see we don't have a model here so we're kind of looking at these things directly. Again, this is not how you would do this if you were going forward to turn this into a concentration game. You would have a model and all that. I'm really just doing like I said, kind of a UI case study just to see what would it look like and then later I would go back and do my actual model or whatever. So this is just a bool to tell us whether two face up cards match. So down here after I've done my first card flip, I'm gonna say if my self, of course, face up cards views match. Then, here I'm going to do the animation. Otherwise I'll going to go on and check just to see if we have two face cards that don't match. Then we'll do that flip down thing. But here we're not going to flip them down. We're going to make them big and then make them small. So we're going to use our view eye view property animator here, very simple to do. We're just gonna say UIViewPropertyAnimator.runnin- gPropertyAnimator. Again I will try to make this a little easier to see by getting each arguments on its own line like that. Okay. So here is the arguments. Properties, animators also very simple. The arguments, seems like there is a lot of them and they are very complicated, but they are really not. Okay. So here we are going to make the thing big. So how long do I want to take to make big? Again not really more than a second. Maybe .6 or .7. These are blue numbers that I would put in a constant struct and then play with to get my animation to look the way I want. And I'm not gonna delay this animation along, gonna start it right off. Really no un-animation options necessary here. Just going to do the animation. So in here is the closure and only thing I can change inside here is those view properties. Okay, if I change anything else, it will do it, but it's not gonna affect the animation in any way. So what are the animations we want to do here? Well we want all the face up cards to get really big. I'm gonna do that with the transform. Transform is really easy to make things big or rotate them or whatever so I'm just going to say, my face up cards views for each again, for each, oops. And inside this 'for each' I'm just going to change the properties that I want to change which is $0.transform=, and I'm gonna get the affine transform identity which is no rotation. And I'm gonna scale it by let's say three times as big. So this is gonna make this view three times its normal size. Let's just do just this part for now. We'll do this completion part in a little bit here. So I just wanna make sure that this is working. So this is gonna force us to find a match just to see if it's working, but I'm sure we can do that. Okay, here we go. I need a match. No, 4 and 9. No, different kinda 4, no good. There's a 9. Okay, so I'm gonna click this card. They're gonna match. And hopefully they'll get really big. And then they're gonna stop because we're not finished with this animation. But let's see. Whoo, okay, it worked that was easy. Now we want the next step of the animation we want to make it small and we're going to do its alpha so that gets small and fades away at the same time. Okay so, we're going to do that with this completion thing that I commented out, put that back, and expand this thing. Remember this one has the position not finished, but position which is either dot end, dot start, or dot current. And we don't really care what it is because we're not gonna have animations that are gonna be jumping on top of each other here. So it's not gonna be a problem. So here we wanna do another property animation. So I'm gonna copy and paste this whole thing right here, put it in here. Okay. And what are we doing with this property animation? Very similar to the other one. But instead of the transform being transform up to 3.0, I am going to make it really small. Down to 0.1. And the same time I am going to say alpha equals zero. Fully transparent. Now, This, remember this is going to happen immediately. It's going to set it to zero, but the user is going to see it, over the course of .6. And actually, this one might want to be longer, maybe .75 or something because remember I went from Identity Transform to 3.0 now I'm going from 3.0 passed identify down to 0.1. So maybe I wanna give it a little extra time, you see what I'm saying? To go that extra distance, so it feels like it's coming out and in at about the same time. But again, these are numbers you tweak over time as you're working on stuff. All right, so again, let's just see if this is working. I have our completion thing in there Okay, we gotta find the other match again. A three, and then an eight, an ace. No, that was ace of clubs and ace of spaded, see? There's the ace of clubs. Is that the ace of clubs? No, ace of spades. Ace of clubs. Okay, ready, here we go. I'm gonna click on this one, now it should go up and then go down and fade to alpha zero. Let's see. Perfect, okay. It worked just fine. Now, once it goes down like that, we want to remove this card, because it no longer can be involved in the matches. So we are going to implement our completion handler right here. And inside the completion handler, we're not going to be doing any view property animation, we're just going to be doing cleanup. For example, I'm going to say here for all these cards, so I'm going to have this for loop again. But not this. For all these cards, I want to set each of them to be isHidden, so I'm gonna hide the cards. Now, I'm also gonna clean up a little bit. Because I really don't like the idea of having these views that are really small transforms. Okay, and transparent, sitting around in my view hierarchy even if they're hidden. So I'm going to go ahead and make the alpha be back to 1. And I'm gonna go ahead and make the transform, be back to being the identity transform. So it's just a little clean up, I just don't like to have messy, weird state views lying around. They're hidden though, so it's not really gonna hurt anything to have them around there. But, for me, it's just a cleanliness thing, to kinda clean up after your animation a little bit. And since there it's hidden, none of this is gonna show. The fact that I'm making it transparent again and changing its transform, it doesn't matter. Cuz this view is now hidden from view. Okay, so that's good, we got that. What's the next thing we need to do, what's the next one we wanna do? We wanna make it so this game's a little harder. I've got this game, and it's actually a little harder than our other concentration game was because we have to pay more attention. Because mismatches, they flip over so fast that if I'm trying to find a match, here, phew, got lucky with a match. If I'm trying to find a match, it's harder to do. But I'm gonna make it even harder by having these cards be in motion. These cards are just gonna be constantly moving around. So I'm gonna have to chase after them to flip them over. And I'm gonna do that with our dynamic animator. Cuz that's the kind of thing a dynamic animator is good. Kinda set them out there and it just kinda floats around. All right, so, how are we gonna do that? Dynamic animator, let's go up here. We're gonna start, we're gonna do the 3 steps, right? Animator, behaviors, items, so let's start with the animator. I'm gonna create a lazy var, which we're gonna call animator. It's going to be a UIDynamicAnimator. And the reference view for it, I'm gonna use the referenceView constructor here. I'm gonna make myself.view, right, the top level view of my view controller, I'm gonna make that be my referenceView. Again, if you're writing an app that had like subviews and stuff, it might well be not be the top level. But this is a demo, I don't have any other views. So that's what I'm gonna have to do. All right, so that's gonna be my animator, great. Now I need behaviors, so I'm gonna create another lazy var here. I'm gonna call it my collisionBehavior. It's gonna be of type UICollisionBehavior. And I'm gonna initialize this one with a closure. We talked about that in lecture but we've never actually done it I don't think. So how do I initialize it with a closure? It's really easy, I just create the behavior. I create a UICollisionBehavior here. No arguments to its initializier, configure it. So the only thing I wanna do with this collision behavior is have the edges of my referenceView keep my cards in. The only thing the cards are gonna bounce off is each other and the edges. So, I'm gonna do this translateReferenceBoundsIntoB- oundary = true. It's a kinda cheap, quick way to get a collision boundary. Then I'm gonna add it to my animator, addBehavior, and then I'm gonna return behavior from the closure. This returns it from the closure, which assigns it to this var, and it's all lazy. We see how we use a closure there, really convenient. Now we have what do we have here? AddBehavior, add the behavior to the animator, all right? So now we have this behavior. We need to do step 3, add the items to it. Here's where we're creating all of our card views. Let's just say right here collisionBehavior.addItem, cardView. Now that card view is instantly going to respect that boundary and start bouncing into other cards. But of course we haven't moved the card, it's not moving, so we need to do that. We're gonna do that with a pushBehavior. So I'm gonna let push = UIPushBehavior. Now pushBehavior's initializer here takes the items that you wanna push. So So this would only be this one cardView right here. I could create a push behavior that puts all the cards in there. But then they would all get pushed in the same direction, I don't want that. And then the mode, again, can be continuous or, in our case, instantaneous. We're just gonna give the card a push and then we are done. And since it's instantaneous, we're gonna wanna clean up after it. So let's give it an angle, so I'm gonna give it a push angle, which is a random angle between 0 and 2 pi. This is in radians, so I am gonna say 2 times CGFloat.pi and it's gonna be random. So I am gonna need to use arch4random. I added a little arch4random to CGFloat as well. We added it to int, but you already knew how to do that, but I added one for float. And then the magnitude, I don't really wanna push it 0. So I'm gonna do a magnitude of let's say at least 1.0, right? But then I'm gonna add a certain amount extra, let's say 2.0 arc4random. So we're gonna have the magnitude and this has to be a CGFloat, right? This is a double, so we'll go CGFloat of all this. Oops, CGFloat(1.0), CGFloat(2.0), random. Probably don't need both of those CGFloats. But anyway, so we create a random. So I'm creating a random magnitude between 1 and 3. Pushing a random amount there. Now I just need to say to the animator. Add this behavior, add this pushBehavior. And as soon as I add it to the animator, it's going to push its items, which is just that one cardView. Now again, we know that this is bad because it's done now and it's never going to get cleaned up. So I'm gonna use the push.action, okay, to remove it from the animator by just saying push. Tell me your dynamicAnimator if you have one, and remove yourself from it. And we know that this causes a memory cycle, cuz this push right here is being kept in memory by this closure. And of course the push's action is pointing to the closure, so they're keeping each other in memory. So we're gonna get rid of this by just saying unowned.push. And yes I could've said weak push and then had an optional chain here, and maybe even an exclamation point here. But if I'm gonna do exclamation point, you might as well make it unowned, because you're unwrapping it anyway. All right, so let's see what happened. So all we did here is we created an animator, a collision behavior, and a push behavior. Let's see what happens, see if it just animates. Woo-hoo, we did it Now, I'm not quite getting what I want here though. One thing is look how quickly it settles down. It kind of settles down too easy, too quickly. That's gonna make it too easy to play this game. I wanted those cards moving a little more. The other thing is I don't think I want them rotated. It's kind of fun to have them rotate actually. But when you flip the card, for example, it doesn't flip along the axis of the drawing. It flips along the axis of the view. So watch this flip, you see how it's kinda flipping around the corner? Maybe that's okay, but I'm gonna decide it's not. Because I also wanna show you how to do dynamic item behavior. Also, the reason that it's slowing down so much is there's not enough elasticity in the collisions. I want those collisions bouncing off and keeping the energy up. So let's fix, and we do that with another kind of behavior, kind of like our collision behavior. It is a itemBehavior, it's a UIDynamicItemBehaviour. And I'm gonna do that with a closure as well. So, we just create the behavior here, oops, let behavior= UIDynamicItemBehaviour. And now we'll just configure this behavior. So I want allowsRotation = false. I don't want those things rotating around. I want Elasticity and see and there's a lot, you're going to have to look in the documentation to see all of the incredible things that you can do here. But I want elasticity 1.0 means that collisions don't lose any energy or gain energy. if I set this to an elasticity 1.1, they would gain a little energy. Those things start going faster and faster and faster, forget it. But if I set it to 0.9, they're gonna slow down. Not as slowed down as much as they are now, but so 1.0 is as kinda the most elasticity I can give it and not run into an accelerating situation. And also, I'm gonna set, I'm not sure what the default of this one is, but I'm gonna set resistance, which is how much it resists forces being applied on it. And I'm gonna set that to 0, I don't want any resistance. I want it to be kind of free flowing here in outer space not resisting anything. So let's add this to the animator, add behavior, and return the behavior. Okay, now is that all we need to do? No, because if we don't add any items to this item behavior, then it's not going to do anything. So same way that we added the cardView to the collision behavior, we have to add it to the item behavior. And as soon as we do that, as soon as we add this thing in, it's gonna be having all those settings allows rotation and all that business. So let's see if that makes things better. Okay, it did make it better. Okay, it's still slowing down, which I kind of want a little bit because I want the person to have a chance to by they're off. They're going too fast, you're just gonna be grabbing after them and then you'll never remember where the other ones went. So I do want it to slow down a little bit. Maybe not all the way to here, I can play with this. Actually have a kind of an idea for how to keep things in motion as the game goes on, but still give people a chance by letting it slow down. But before we do that, I wanna talk a little bit about that creating a UIDynamicBehavior subclass, where we combine other ones. Because look, we've got three different behaviors here. We've got the collision behavior, we got the item behavior, we got the push behavior. And we're having to do all three different pieces of code for that in our view controller. It'd be a lot nicer if we had another behavior called the card behavior. And it had all those as part of it. And then we just add the card the card behavior, and then we'd get the collision and the item and the push automatically. So that's what we're gonna do. Let's go up here to File>New>File. I'm gonna create a Cocoa Touch Class, a UIDynamicBehavior as a Cocoa Touch Class. Here it is subclass of UIDynamicBehavior. I'm gonna call it CardBehavior. I'm gonna put it where all the rest of my files are there, don't forget that. Here it is, CardBehavior, a subclass of UIDynamicBehavior. And all I'm gonna do is go over here and grab all this stuff, and take it outta here and put it over here. Okay, so I'm just putting those behaviors over there, even the push. I'm gonna take the push out of here as well, put it over here. The push I'm gonna put in a little function, a private func called push, which pushes an item. This can all work on UIDynamicItem, I have UIViews but there's nothing in my code that's specific to UIView, it's all just dynamic items being animated here. So we'll put all that push stuff in there. Now we're gonna have to fix some of these little copy and paste problems. But this is essentially how you create your own dynamic behaviors by doing these. But these have to be added as children and we add them as children in an init. So 99.9% of the time, when you create a dynamic behavior, you're gonna override init with no argument. You're gonna call super.init, and then you're gonna add your children. So I've got my collision behavior, I'm gonna add, I've got my item behavior, I'm gonna add. Now, I can't really add my push behavior here, because the push behaviors, I need to know the item. So I can't quite do that yet. So and obviously I don't need to be adding them to animator right in here, cuz there somebody's gonna add me to an animator and that's how these things will all get put in an animator. Same thing here. So how am I gonna deal with this fact of push? Well, I'm also gonna add a func add item which adds an item, a UIDynamicItem. And when I add it, I'm gonna add it to all my children. So I'm gonna add it to collisionBehavior.addItem, I'm gonna add it to my itemBehavior, And I'm gonna push, so I'm gonna push the item too, which is essentially adding it to the push behavior here. So let's fix this one up, this needs to be item not cardView right here you see that argument, right? The push action here, notice that we're removing the behavior directly from the dynamic animator. Actually what I wanna do here instead is make it a child, so I'm gonna add a child behavior which is the push. And then when I remove, instead of removing behavior directly from the animator, I'm gonna remove my child behavior. See, now there's a little thing going on here, it's gonna complain once it compiles here. Implicitly use of 'self' in closure, use 'self'. In fact, I'm gonna do this if I can by just typing self., and this is now capturing self, which is the dynamic behavior. Yikes, okay, we don't wanna do that because the dynamic behavior definitely has a pointer to this closure, because it has a pointer to its own child behaviors of one which is the push behavior. And the push behavior points to it. So I'm gonna get rid of this by saying, weak self. Okay, phew, broke that thing. I still need unowned push because I'm also passing push right there. Now I don't wanna do unowned self there, because just in case for some reason this whole behavior got removed from the heap, I don't wanna be crashing here. So I'm just gonna do a weak to be a little bit safe. It's not the same case where I know for a fact that this push has to be in the heap cuz I wouldn't have got here. That's not necessarily true of self. So we are push there so, now that we've collected all of our code right here, actually I'm gonna add one more thing, cuz we haven't seen this as convenient init. Okay, I'm gonna add a convenience initializer here, that let's me specify the animator that I wanna be in. I just wanna show what it looks like to create a convenience init. And all you do when you create a convenience init is you can all a self.init only on yourself, then you can do what you wanna do. In this case, I'm just going to tell that animator to add behavior myself. See how that's like a convenience for me to be able to create one of these and have it automatically added. So let's use that convenience initiator, initializer back in our view controller here, actually we can go back. Here it is, and instead of adding each item to both collision and item, we just add it to a cardBehavior, which we'll have to create. So I'm gonna have cardBehavior, add the item to cardView, and then I'm gonna create a var up here, a lazy var. It probably doesn't even need to be lazy, yeah, it probably does. Lazy var, which I'm gonna call cardBehavior, which is just gonna be a cardBehavior. And I'm gonna use my nice convenience initializer to say in my animator. Okay, so this really cleaned up our code here in our view controller. In fact, there's only two lines of code at all that have to do with our dynamic animation in this thing. We've moved it all to that nice thing. Let's make sure we didn't break anything by doing that. We didn't. Okay, it's still working. And we can still flip out cards over. Those are separate animations going on over there. Now, I wanna do a couple of other things here. One is ,when you pick a card I want it to stop. I want it to stop being animated. You picked a card you get to take a breath, and look at your card. And then, if you flip the card back over or if it match it, or if doesn't match and it gets flipped over for you, then I'm gonna give it a push. Because see how everything's stopping, so now, choosing cards, we'll start pushing them a little bit and getting them going again. So, yeah, you can wait for the whole thing to slow down and stop and then you can pick a card. But once you pick them, they're gonna all move around again and be shuffled up again, make it hard on you. So that's seemed like a good compromise to me between constant movement and all sitting completely still like this. So how am I gonna do that? Well that's super easy to do now because I have that one behavior. And if I remove an item from that behavior, it's going to automatically stop animating. Because nothing will be behaving. There will be no behaviors operating on it. Okay, so how do I do that? Well, I'm just gonna do this right in my flip card right here. As soon as I get a touch that's gonna flip a card over, I am just gonna say cardBehavior, not that. CardBehavior with a lower case C, cardBehavior remove this chosenCard. Okay, but I do wanna add it back to the behaviors. When do I want to add it back to the behaviors? Either when, I didn't add remove item, let's make sure we do that, sorry. Where is our behavior? Yeah, sorry, we have add item, we need remove item as well, okay. And removeItem is exactly the same kind of thing, except for that we are removing things from all of the behaviors. Remove, Sorry about that, forgot about that. Remove, and, we don't have to remove the push because we automatically remove the push as soon as it happens, right? Here's where we're removing the push. So since we removed the entire push behavior we don't have to remove the items from it. So there's removeItem, sorry about that. Okay, clear, all right let's go back to our thing here. So now we can do a removeItem, but we wanna put them back in, when do we wanna put them back in? Well if you just flipped the card face down, we wanna put it back in then, or if the two cards gets flipped down for you automatically because you had a mismatch, then we also wanna do that. So, let's go put that, and we're just gonna put add item to put them back in those cases. So, where are those in here? Let's see if I can find them. Okay, so here is the one where two cards don't match and we're automatically flipping it face down right here, so I'm just gonna have this cardBehavior, add item, the card view. Okay, let's put it back in. It got flipped face down automatically, and then here's the else, oops and of course self. Does that cause a memory cycle? No because these are animation system closures. We're okay, all right? So here, this is in the case where there weren't two cards. There was only one card. And if you flipped the card face down, if the chosenCardView, it's face down, so not face up. So you pick the card, it's face up then you picked it again, it went face down, then here I need to have the CardBehavior right on itself there, because I'm not in a closure add item, put that cardView back. Now what about cases were the cards match? Don't need to put them back because they go away, right? We remove the areas hidden, so there's no reason to put them back, right? And it's not cardVew, it's chosenCardView. Okay, so let's see if this works. Oops cardBehavior, it's right in it's in the completion handler. This is in a closure, it's in the completion handler up here of this initial flip. So does this cause... every time you have to, any time it says this to you and you put self in there you wanna go, hmm, does this cause a memory cycle? No, it doesn't here, we're okay. It's just a completion handler closure, we're fine. Okay, so now when I click one, if I can get one, there it is. It stopped, see, it's not being bounced into or moving in any way. And now if I pick another one, they don't match. Now, they both get a push. Because every time I add an item back to my cardBehavior, it gets a push that's kind of cool, kind of a nice little feature. Same thing here, oop they don't match they got pushed. Okay, these don't match they get pushed and hopefully if we find two that match somewhere, somebody help me how do they know where one is. There we go, we gotta match, okay, anyhow. So this is kinda nice, it plays kinda nicer. But there's still some things I don't really like about this. One is, I really don't like things on the edges so much. I don't mind if things stop, but I'd really rather they be towards the middle. And so, I have a little fix for that, which is over here in my cardBehavior here. When I push, instead of pushing at a random angle, instead, I'm going to push, and we don't have time for me to type it all in. I'm gonna push towards the center. So depending on where the card is in its reference bounds, the reference views bounds, I'm gonna push towards the center. So this is the process when you do animation of kind of tuning your animation. Seeing what makes sense, what doesn't make sense. So this is a nice tuning thing to push towards the center. Another way that we can tune our animations here is to slow them way down. Because when they're going so quickly sometimes they're not quite working and we can't really tell. So let's go back and slow down a couple of our animations to see how they're doing. So, for example let's load down our animations of the matching cards. So when the cards match that to right here remember we're scaling it up to three times and we scale it back down. And we had out alpha to zero. So instead of this taking, you know, about a little half a second, three quarters of a second, let's slow it way down to take six total seconds, three seconds. For each part of the animation and see if our app is working well doing that, so we're gonna find any problems. Okay, it's gonna require me to find a match. Let's see we got here search around from cards til we can find, I think, nope, no well, there we go. So it's going so slow, but look what happens if I click on other cards. They're still able to match, in fact I can select more than two and then they disappear. Okay why do those disappear? Well that's because this second animation that comes along sets their alpha to zero, picks them up along the way while this long animation is happening. So we really need to more careful when we're doing this second half especially of this animation, that we're only working on the cards that were involved in the match. And there's a couple of ways we can do that, one for example here where we're deciding whether we wanna do this animation or not we could just make sure that are faceUpcardViews count is always less than 2. Because if we already have two matching cards that are expanding and growing out, then we obviously can't match anymore. So that would kind of work except for that you can imagine if I had a match and the cards are expanding out that it might actually want to start working on my next pair. And so for that to work, we really wanna have those two matching cards not really count as face up cards at all. And we can do that up here. Here's where we decide what a face up card is. Currently it's a card that is face up and obviously is not hidden. And we could just enhance this a little bit here because we know that those cards that are expanding are either scaled up to 3.0 in their transform, or their alpha is not 1, right? It's 0, in fact So we can just put those on here. So lets say a face up card also has to have a transformed that does not equal the identity transform scaled by 3.0, 3.0. By the way you can see the terror and danger of blue numbers because if I were to ever change this number and forget to change the same thing here, then this would not work. My animation would be out of sync with what I consider to be a face-up card. So this is where we'd wanna create a struct, a private struct and use statics to have those be constants. So we also want to make sure that our alpha here is equal to one, in other words, we're only going to consider card face up if it's fully visible. The last thing we're going to need to do here also though is when we go into our animations, notice that we look for which cards are face up. By calling this self.face up card views, all right? And that is this thing right here. Well this is dynamically always calculating the cards that are face up, so if we have an animation here and it starts off and is going and then it tries to do its second part, it looks for the face drop cards again. When in fact, we just want this entire animation to apply to the original two cards that were chosen. So we can capture that by just having a little local variable. Here I am going to call it cards to animate that equal the face of cards at the time we start the animation. Okay so we are sort of capturing them here. And then going through and using this. So everywhere we're re-looking up these face up cards instead we want to use the cards to animate. So let's go down here, this one, this one, this one, I'm just searching and replacing here all of those cards to animate. So let's try all of that and see if that has made it so that our nice, winning animation doesn't have that same problem. So, I've gotta find our matches again, whoops, let's see. There we go, there's a match. Now I'm gonna click on other cards and it's able to match them. And that long animation was still continuing. So that's much better UI. Now of course we're not gonna have our matching animation take so long, but by making it take so long we are able to find that problem. So let's put that back to what it was. I think we had like .6 and .75. Let's take another animation and slow it down, how about the animation where the cards don't match and we flip them back over? Okay, that's this animation of it flipping over and then here is where it flips the two mismatching cards over. So lets slow both of those down. So that one down and slow this one down here. This was the matching animation. I want the, the original flip animation there. All right, so we click, okay our original flip animation. Okay, it's going real slow, nothing seems to be a problem. Let's try another one that goes slow. And they both, okay. It all looks okay. Let's try clicking a little faster. How about this one and this one? All right, they both go slow. Wow, what's happening? Okay, what happened to that ten, that second card? It kind of, it didn't flip over. It kind of jumped, and then it flew around. Let's try it again to see what's happening here. Let's try this one and this one, here we go, whoa. Okay, so something is clearly wrong here. So what is happening here that's making that card go so wacky? Well, what's happening here is both the original card, that we flipped up, and the second card are both trying to flip the two cards face down. Okay, and so those two transforms, those two modifications here via transition, they're both trying to operate on the cards at the same time. And when you have something in the middle of transforming and another transform comes along, it's basically messing up the whole transform. Now, an easy fix for this one too which is let's just always let the latest card that was chosen control the animation. That way they'll never interfere with each other because when the second one comes along and chooses it gets to do the animation. So let's do that by just creating a little var here, to keep track of our last chosen card view which will be a playing card view of course. And it'll start out nil, that's fine cuz we don't have a last chosen card view when we start out. And so every time that we go through and choose a card, okay right here, this is where we choose card. We're just gonna remember that our lastChosenCardView = chosenCardView. And then when we get down here to this animation of them closing down here, we're only gonna do this if the chosenCardView equals the last chosen card view. So, that way, this animation right here can only happen, can only be controlled, by the last card. And they won't interfere with each other. So we need a self right here and let's try this. All right so here's this one, it started animating, start this one and it worked. Okay it let the first one finish its animation and then it didn't do it, didn't do the flip and waited for the second one to do it. Okay, so these are the kind of things when you're tuning your animation, that slowing them down will help you find these overlapped things. And you might think well, these cards are flipping so fast, users would never be able to do this, but they absolutely can. Users tend to click, click, click, click, click. Oops, they're clicking quickly to try and make things happen, so you wanna be prepared for that. So I'm gonna put these back to where they were. What do I have, I don't know, 0.5 or something, 0.6, something like that. Okay so, here we got this guy and that guy. Let's see if we can find a match here somewhere. This one. No, it's hard to find a match at this game. It's quite a difficult game. I found that I'm not really very good at it. There's that jack, I think that one there. Yeah, there's a match. No that moved. King over here? There we, that was king of clubs, or king of spades. All right, well anyway, there we go, there's a match. Alright, I believe that's all I have time to show you. I think I've got through most of the stuff I want to show you, so your homework is just to animate your set. In very predefined ways. So you'll be using these exact same three mechanisms. Transitions, view properties, and the dynamic animator. All right? I'll see you all next week. >> For more, please visit us at stanford.edu.
Info
Channel: CS193P
Views: 10,670
Rating: undefined out of 5
Keywords: iOS, Swift, Stanford, CS 193P
Id: wERNQyfJYLo
Channel Id: undefined
Length: 87min 1sec (5221 seconds)
Published: Mon Jan 01 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.