iPhone • 50:54
The iPhone SDK provides a rich library of views and controls that you can use to build your user interface. Find out how to add your own capabilities to custom views and controls and make the best use of Core Animation to produce a truly unique iPhone application. Understand the design decisions and best practices for drawing and layout that go into a polished user interface.
Speaker: Dan Keen
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
My name is Dan Keen, I'm an iPhone applications engineer and I'm here to talk to you today about iPhone views and animations. But before we start, I just want to give you a demo of what we'll be able to do at the end of this session. So what we have here is something that I sort of like to Call System Emulator. It is an emulator for an old 1950s IBM 1401 computer, a good use of modern hardware.
The Computer History Museum down in Mountain View is actually restoring one of these and I spent a little time volunteering there and I figured what better way to get to know the machine that to write an emulator for it and I thought it would actually be a pretty cool demo for WWDC 'cause it does a bunch of things.
Up here we have a print view basically. It's an old green and white with you know the dots on the aside and it's actually fairly white as you can see here 'cause it prints a 132 characters. Here we have the address field which is the next program instruction counter. It's actually in binary-coded decimal with a check pit here for the parity, so 8, 4, 2, and 1 for each actual 10s digit. We have the A&B location registers.
This machine actually has no registers at all. Instruction length, op codes, and we have these bunch of lights just like you'd see on the front of a real IBM and then we have three buttons here that are made to look like the buttons on the front of the IBM, the "Start," "Check Reset" and the "Stop" button.
So if you were to actually hit the "Start" button, you'll see the lights are going to blink a bunch and then it's going to go off and start calculating powers of 2. The 1401 was a business machine. It actually has an unlimited word length, it will calculate as long as you want, this will go until it fills up the print view, and you can stop it. You can decide, oh I want to tear off this page of paper and hand it down to accounting.
So you know you tear off the page, then you start it up again, it picks up where it left off and well clearly this run off our space so we can drag it over here and it's running while we're dragging. And then you can decide, oh you know actually, I kind of want to go on the instruction at a time so you pull out the little sheet here and you say, all right let's run at a single cycle and so then it goes one instruction at a time and likewise we can actually set up an address to stop at here.
This is much easier on the phone itself to spin those wheels. So if we set that breakpoint then we'll see that it prints one line each time so we're using supports breakpoint mode. So that's my cute little demo and what I'm going to show you at the end of the day, there's a lot here. I clearly can't cover all of these. I'm definitely not going to cover any of the emulation itself because that's not really pertinent but what I will cover is how to do this light panel in the middle which is probably the flashiest part of it.
You know, it's blinking lights on and off constantly. So, but we need to go through some things before we can get to the point where we do that. So let's start off by saying, well OK this was Views and Animations, so first we need to talk about views and what are views. So this is an example from the Address Book, this is an NavBar at the top, and you'll see that there are actually three subviews in this view. We have the Groups View, the All Contacts TXT and actually the "+" button on the other side.
We're also going to talk about controls because, you know, with not being able to interact with your software it's really just going to be a pretty seam demo so in the same example, we have two controls, the Groups and the "+" button. So we're going to talk a bit about them and then finally we're going to talk about animations. Animations are very pretty, everyone loves animations but we're also going to you know, say they provide a lot of context to the user.
So in this example, what happens when you actually click the "Groups" button, a bunch of animations kick off that you might not have ever realized. The Groups is going to slide over and cross fade into the Groups TXT. All Contacts itself is going to slide over and fade out while it's doing that.
And the "+" button is actually going to remain stationary and just fade out. So we actually have three, well four different animations if you count the Groups TXT that's coming in across fading with the "Groups" button. So in slow mo you can see all those animations happening at once and the reason we do all these is to provide context to the user.
The views are sliding, so you'll actually see the view-- the content below the sliding but the bar is going to remain in the same place so in order to provide contacts there, we're doing this nice animation and then when you click on Group, we'll do the exact reverse animation to bring you right back to where you were. So those are pretty much the three kinds of things we're going to cover today.
So we'll start off with a view. What is a view? So a view inherits from our pseudobase class NSObject. From there we have UIResponder. A UIResponder is responsible for handling events and forwarding them up the responder chain. I'm not really going to talk much about UIResponder or the responder chain, stuff like that, or events that much in detail here. If you want to know more information about that, on Thursday morning at 9, in this room is processing multi-touch events on iPhone and I would highly recommend going to that.
It will be a very interesting lecture. From there we get UIView. So you can see it's actually a very simple inheritance hierarchy here just NSObject, UIResponder, UIView and the UIView is responsible for actually drawing the content on the screen, making the pretty actually happen. So we know that UIView is responsible for that. We need to define some things in order to make them work properly.
The first thing we need to do is we need to have a coordinate system so we know where we're drawing. If you're familiar with other API design, for instance App Kit, the origin is in the bottom left. On the iPhone, the origin is actually in the top left and as Y increases, it moves down the phone, as X increases it goes from left to right. So we have a coordinate system that's actually pretty straightforward.
Then we need to define what the views themselves are. So Views are actually just rectangles. Easiest way to remember, the view is always a rectangle and you define it with its frame. So a frame will have an origin, in this case we have an origin of 140, 30 so we are over 140 pixels from the top left and down 30 pixels and the frame also has a size parameter and in this case will be 200, 100. So this view is 200 pixels wide, 100 pixels tall.
We're not defining where the lower left-- lower point there is in coordinates, we're actually defining as a size so based off of where the origin is. So this defines in the superview, so you can take a view and you can stack it inside of other views and the frame will give you the coordinate system in the superviews coordinates. There is also Bounds which gives you the coordinate system in the view's own coordinates.
So in that case, the size is going to remain exactly the same, at 200, 100 but the origin actually uses 0, 0 because remember we've now translated the coordinate system to the NSViews coordinate system. That's pretty straightforward, it's you know, you sometimes have to wrap your head around it a bit but it's relatively easy to do translations in that respect.
It becomes more complex when you have transforms on views. So a view has a property transform which you can give a CGFI transform. So let's say we took a 45-degree counterclockwise rotation and applied it to this view. In this case, the bounds is going to remain exactly the same 'cause remember that the bounds is in the views' coordinate system.
So it doesn't care what transform is applied to it. It's always going to be consistent with the view itself. So the origin is still going to be 0, 0 and the size is going to be 200, 100. That's relatively straightforward. What happens to the frame though when we have a transform? Frame is a lot more complex.
Because we said views are rectangles. So you can say OK it's a rectangle that's rotated, it should still be the same thing. But the rectangles in the parent's coordinate system, the parent doesn't care that the view is rotated, it just cares that it's a rectangle. So the frame is actually the bounding box around this view. In that case the origin becomes a 133.9, negative 26.0, and the size becomes a square at 212.1, 212.1.
There's no way you're going to ever be able to calculate this properly yourself, so generally if you're dealing anything with transforms which either you could be doing yourself or during a rotation animation or something like that, it's much easier to rely on the bounds. However, the bounds as we saw, the origin is in its own coordinate system so we can't position views with bounds.
So how do we actually position the view? There's also a center property The center defines the exact center of the view. So this will remain the same no matter what transform you imply, if you rotate the view, if you scale the view, it's always going to be scaled by the center, so it's easy to always rely on the center property as being an easy way to position the view and the bounds as a way to change its size.
So you could say you know, OK always use bounds and center but the thing is, it's a lot easier to just use frame and so you'll find that myself and many other people just use frame most of the time and only worry bounds and center when we need to.
All right, so how about combining views. We know that we can stack views within each other, so let's say that you had a wide superview and inside of there you wanted to put another view, so you have a subview but this view is going to be tall and skinny and you would add it to it.
It would actually end up looking like this on the screen and why is this? This is because we actually do not clip to bounds in the superview.
If you have a subview that extends beyond the bounds of the view, it's not going to be clipped. This can be useful. This can also be bad.
Most of the times you're probably not expecting this to be what happens. So if you want to change UIView setClipsToBounds:YES. Now, why is this off by default? Well, first of all this is the result we get. It's very expensive to calculate. In this case it's pretty easy because you can blit to a point and then we can say OK this is where the end of it and so we can stop blitting.
If we rotate 90 degrees then we can blit in columns and that's still pretty decent but if you're ever rotated at 45 degrees or anything like that, then you have to do a stride and that becomes much more expensive, drawing becomes much more expensive, it's generally bad and so because of that, this off by default. You're welcome to turn it on as you need it but realize that there is a drawing penalty here. So now you have your views, you have your subviews, you can decide whether you're going to clip or not.
Now you want to actually position them. So you can position them really at anytime you want and I'm here to say always position your views in layoutSubviews. So let's say we're adding a bunch of views, so we have a green view, a blue view, a red view, a silver view, a light blue view, throw them only into the origin when you start. And then in the layoutSubviews call, you're going to want to actually position them properly so then you can have a nice view. We'll get a little bit more into that in a second but first, why layoutSubviews? Well for one thing it's fast.
Let's say you have a view and you're application is running. So the NSRunLoop is running. I'm not really going to get into event processing here or the run loop or anything like that but eventually something is going to kick off either your code or the systems code running. Say an event comes in or a source fires and NSTimer fires.
At that point you may decide that your view needs change. So how do you change the layout of your view? You actually want to call UIView setNeedsLayout. And you'll call that on the view that needs the layout and then you'll return to your code and you may decide, oh actually something else has changed because now you've checked something else either on the server somewhere or whatever, so you need to do it again and so you'll UIView setNeedsLayout. This is awesome because setNeedsLayout is just basically a flag change. It's very lightweight, there's a very little performance head to calling it. It's a good idea.
Then eventually your code will return to the run loop. At this point, at the end of the run loop, it will actually call UIView layoutSubviews for you. So it'll collate all of this setNeedsLayout that will happen during this run loop cycle and it will call layoutSubviews just once for them. So this makes it vary performing in order to be able to lay out things properly so you don't lay out many times in between. Some tips and tricks about positioning your views. First of all, like I said before, don't position views in either loadView or initWithFrame.
It's really easy to that because you go, oh well this is when I'm actually adding my views to my hierarchy, I know where they're supposed to go, I'm just going to do that. Every time I personally have done that, I've ended up regarding it later. Because if some thing comes up, you decide, oh I need to just support landscape orientation or something somewhere has changed and the views have just shift a little bit and if all of your logic is an initWithFrame, you then have to duplicate that logic elsewhere.
So just put all into layoutSubviews. Second thing is don't ever call layoutSubviews directly. Like I said earlier we call setNeedsLayout and then the system itself will call layoutSubviews when it needs to. Of course, you will need to call super layoutSubviews on occasion when you're subclassing something and it needs to pass that call back up to its super in order for things to be laid out properly.
If you really, really need to have a layout done, use UIView layoutIfNeeded. Now why would you use this instead of calling layoutSubviews? So remember we had this setNeedsLayout that gets called whenever something actually changes that your view needs to handle. So if you use layoutIfNeeded, it will return early if that hasn't been called yet and you can assume that the view is laid out to how you want it. Finally, you actually don't need to call setNeedsLayout yourself most of the time. Sometimes you do if you know that a model change has happened that you're going to want to reflect in the view.
But for instance, if just the size of your view has changed, the system itself will call setNeedsLayout and it will automatically then call layoutSubviews as needed. So here comes the first demo where we decide that we need to lay out the lights on the front of the panel for this thing.
So for those who have come in later, this is going to be what we're going to end up designing at the end and what we're going to actually lay out are these lights here. So if we get rid of the final version and go to here, you'll see we started out with what's basically a blank light panel here, we have no views at all.
The first we're going to want to do is actually add the views themselves. Now I'm going to add a comment here just to kind of drag this home, set up the different light views, size them to fit now, they won't change later and we need to-- so we don't need to update the size every time in layoutSubviews. Don't position things here, just add it. So we have this light view which is going to have four digits, this is going to be the program address.
We're going to set its title to be address and we're then going to call size to fit, so the light views themselves are actually relatively smart. They know that if they are four digits, they are B, C, D, they just need a C, an 8, a 4, 2 and a 1. They know exactly how wide and how tall they need to be and so they will set their size properly and then we add the subview to ourselves.
So if we're going to run this now, we'll see that we have a nice ugly gray box up in the corner here. It has no positioning information. All right, well let's add the views just so we can have a bunch of ugly gray boxes all on top of each other.
So we add the other views and now as expected, we have a bunch of ugly gray boxes. They're all just on top of each other. We haven't given any position information but you'll see that they all have slightly different sizes because they're relatively smart and they know that they need to size to fit. So the light panel itself and where we're adding this, it's in initWithFrame the light panel itself inherits from UIView, so because it's a UIView subclass we can just actually create a layoutSubviews implementation, like so.
And the first thing we're going to do is we're going to go back to that original program address that we did and we're going to lay it out. So remember that we did this size to fit earlier, so the size for this view is already correct, we don't want to wipe that, so we don't want to actually change the size parameter on the frame at all. So the best way to do that is to just pull out the full frame initially and then just change it's origin to this point which you know we've calculated earlier.
Our HI designer decided to spec that this is exactly where it should be and then we say set frame on it. And you'll see that now we have a nice ugly little gray box that's in its proper location. Note that we never called setNeedsLayout here or anything like that. We never called layoutSubviews directly. We never called layoutIfNeeded. This just automatically got called for us.
So let's make sure the other gray views also go into the right place by adding the rest of the code here. They're all basically exactly the same thing and now they're all laid out to their right positions. They don't look that great yet but at least they're in the right places. So as you can see that the rest of them is just a copy of the code above with different orgin points. So we'll get to actually drawing them in a second.
But first we need to know how do you actually do that. So what is UIView? We know that it, you know, inherits from UIResponder and from the NSObject, but what does it actually do? It's actually just a thin wrapper around a CALayer. A lot of folks end up going directly to CALayer because it's something that they're familiar with.
Either they're coming from the desktop or they've seen all the really cool CA demo, you know demonstrations. The UIView itself is actually a relatively thin layer that gives you a bunch of added functionality so I'd recommend unless you absolutely need to move to CALayer to just use UIViews. The CALayer instance itself that is generated is a read-only instance.
You are not allowed to change the CALayer. However, you can change the type that gets instantiated. So if you have a UIView subclass and then you then override layer class, it will then instantiate whatever layer type you want, for instance a CA tile player as seen. The delegate for the CALayer is the view. A lot of folks have tried to change this and it ends up having disastrous effects especially as we read the software.
Let's say that CALayer gets a new delegate call. If you change the delegate to yourself, then suddenly the UIView isn't getting that new delegate call and you may-- we, you know, we maybe depending on that delegate call being passed to UIView. So if you do want to override down the delegate stuff, in your UIView subclass, just override those actual delegate calls that you care about.
And because CA-- the UIView is a very thin wrapper around CALayer, it's a one-to-one relationship, things like frames, bounds, center, background color, all kinds of stuff like that are directly mapped between UIView and CALayer. So how do we actually draw? Well drawRect is called for a UISystem just like layoutSubviews gets called.
So don't ever call it yourself expect again to super drawRect as needed. LayoutSubviews you can call for yourself, you know, call by yourself if you want to. It's expensive, it's wrong, you should be calling layout if needed but it's not really going to break anything. DrawRect, you really do not want to call by yourself.
There's a bunch of context that needs to be set up before drawRect gets called and by just calling it yourself, that's not going to work properly. Use UIView setNeeds Display just like UIView setNeedsLayout. Just like that one, it's a very simple call, it's quick, it just sets a flags, you can call it multiple times, there's no penalty. DrawRect is only going to be called once at the end of the run loop. Unlike layoutSubviews, there is no corollary for layoutIfNeeded.
There is no drawIfNeeded. During your drawRect, only draw. That's kind of you know simple, well duh, but it's actually very tempting to say oh OK, we'll I'm doing this, I shall actually calculate what I need to draw now and that's expensive and maybe I need to layout something.
So don't layouts in drawRect because it's just not a great time to be doing it. You should be doing your layout in layoutSubviews and don't do calculations, don't do expensive operations, drawRect just draw and return. If you need to do anything, do it in advance or do it afterwards.
Don't do it while drawRect is actually happening. So you can actually do very complex drawing in a UIView. So UIView is a rectangle and you're welcome to draw whatever you want in that rectangle. You can say, all right I want a red box up here and I'm going to put some text in it and I want a purple box down here and I want some text in it and I want this nice little picture frame and in the picture frame I'm going to have five suns all popping in. You can do all of that inside of one UIView.
The times when you'd want to split that up between multiple views or if any of these has to animate, if any of these has to, you know, reflect a layout orientation, like if you rotate to landscape, if things need to stretch or shrink and stuff like that. But if you know that your view is static, it's perfectly fine to just draw it all yourself.
It's going to be much faster than having multiple views compositing. That being said, once you draw, the contents are cached eternally. So if for instance we were to draw some text like this drawn text and it animates on the screen like-- nicely like that, it does not redraw during this time. The contents have been cached.
Once you've drawn once, we save that and that's actually what we blit to the screen at all times. So you're never needing to draw again. If you dirty a subrect of this, we setNeedsDisplayInRect which we'll get to a little bit more. You can actually respect that in drawRect and then draw within what you've drawn already.
So in this case we would not have to draw drawn text again. We could just draw subtitle. When you get drawRect you're going to get a parameter which is the rect that you're supposed to be drawing in. On an opaque view, this will be field by whatever background color the view has. If you have a transparent view, it's actually filled with transparent pixels. That may not be what you want.
You may want to do a bunch of complex drawing and tile things on top of each other overtime. If that's the case, call setClearsContextBeforeDrawing:NO and we will not fill it with the transparent pixels, it will just be presented to you as the way it was when you last drew in it. This is only true for a transparent stuff.
For opaque stuff, it will automatically be filled with the background color. So hints and tricks for drawing in UIViews. The UITableViewCell subclasses cannot use UIViewDrawRect. Don't use it directly there. They have a content view. If you want to do drawing like that, add a custom subview to the content view and draw there.
For more in formation on this and a bunch of other table view stuff, hopefully you were here just a few hours-- an hour ago for the Perfecting Your iPhone Table View Session which was in this room. If you haven't the slide should be available soon and the videos will be available. I highly recommend seeing this talk.
Use setNeedsDisplayInRect to dirty particular regions. So we were saying setNeedsDisplay earlier. Let's say you had a huge UIView like that complex drawing view that we had earlier. Well if you really just care about one of the suns, you want to make that sun brighter or make it a moon or whatever, just dirty that particular region. What this will do will save a lot of time because you won't have to redraw all of that stuff and you can just redraw that little snippet.
And like a corollary to that, drawRect passes a parameter so you should respect the rect parameter that gets passed in. That really is telling you what has been dirtied, what you need to redraw. Most of the time if you're not doing, you know dirty drawing of certain just rects, you can pretty much ignore it and say well the whole view has been changed so just draw the whole view. But it's better to, if you need the performance, to just draw the parts that draw rect gives you. And so now we're going to actually make the lights have some content.
So if we come back here we'll see that the lights are just these nice little gray boxes, they're not really showing us much. But we need to actually make them draw. So we're working with light panel class before which was this whole big slightly bluish gray background here. Now we'll work with the gray scales themselves which are the light views.
So the first I'm going to do is in our initWithFrame, I'm actually going to schedule the timer. So this is a little heavy handed. It works in this example. If we would have a bunch of light views, it kind of doesn't make sense for each of them to have their own timer but for this example, it's relatively simple to just have a timer per light view.
We're going to have it update about thirty frames, it's going to call dirty rects which we will make smart in a bit and we're actually going to add it to the UITrackingRunLoop mode. What's the point of this? If you saw earlier when I was dragging the print view, everything was still updating nicely. The reason that was happening was because our timers were actually firing during the tracking run loop mode. When a user has a finger actually down on the screen, the run loop switches modes to UITrackingRunLoop mode.
Most of the time this is great because you don't want to have expensive timers going off and interrupting stuff when the user is dragging, you want the dragging to be as fast as possible. In my example, it makes a lot more sense that the machine keeps running while you're dragging the print view, so I have it actually update during that time by adding the timer to that run loop mode. So then we're going to actually going to have a drawRect which we're just going to put at the end here.
So our drawRect is going to start out by taking the value that has been assigned to this light view. So this app is actually multithreaded. The emulator is running on a background thread and it's constantly putting out updates. We're only going to be updating every thirty frames a second, so every time we draw we're going to try to pull out that view.
In order to that, we need to do a little locking, pull out the value and unlock. We're then going to do our nice little border. So we're going to do that by setting a color into our drawing context and then calling UIRectFrame. What this does is it draws a one pixel border around this rect. So the rect that passed in, we're just going to assume for now that the rect that gets passed in is our full view.
If we have a title, so if it needs to say address or op code or whatever, then we also want to draw that light gray background behind the address text. So what we do is we create our title bar frame which is sort of that rectangle and we call UIRectFill. Just like UIRectFrame this actually creates a rectangle and fills it. We're going to be using the same color that we set before.
We don't need to set it again or anything. It's set until we set a different color which we'll do next by calling UIColor blackColor set and then we'll call titleDrawnRectWithFont, lime brick mode, tilt truncation, alignment center. Tilt truncation really isn't needed here because all of our labels fit within their views but text alignment center, we're actually giving it the full title bar frame rect and by calling it with a text alignment center, it will actually center the text in the rect.
So if we were to run this like it is now, we'll see that we now have these nice frames around so the UIRect frames are the nice frame. We have the title bar frame here and we have the text as well. That was really pretty easy. But there is no lights yet.
So the lights are you know, they're a little hard to calculate. We have this draw number here and what this is going to do is it's going to go through a bunch of code, that goes, oh you know, is this bit side, is this bit side, does my light need to be on and all those stuff.
Really you don't need to worry about all that. What you do need to know is that eventually it will call draw highlighted string, string with font and color at point, just my own little code. And this is a little trick. To give the text that we're going to draw on the screen at some depth, what we're going to do is we're going to draw a highlight color right above it.
The highlight color is going to be white and so we'll take whatever point was passed in. We'll just subtract one and we'll draw the string at that point with that color. And then we're going to take the color that was passed in. This color is basically whether the light should be on or off and we're going to set that color, we're going to move back down a point to what was originally passed in and we'll draw again.
Awesome. So you can see that like white highlight color there behind the 8, for instance. I don't know if the zooming might help, it's all pixelated now but you can see that the white sort of gives it this depth look to it.
So now we have this timer running. We're actually drawing the lights, we're drawing the title, we're drawing the frame and if we were to run, you can't really tell because this is not an iPhone but it actually doesn't run that well right now and why is that? So every 30th of a second, we're actually drawing this title again.
We're drawing the frame again. We're drawing a bunch of things that are never changing. So we need to make that a bit smarter. So we come to our dirty rects, you'll see that all we are calling was setNeedsDisplay and that's, you know, that's true, we need a display but we really need it to update just the sections that we need to. So what we can do here is we can say do we have a title or not? All these things have titles.
If we don't, we'll just call setNeedsDisplay because all we have is a frame. But let's say we have a title. Then we get our rect, we're going to get our rect from self bounds. Remember bounds is coordinates in our coordinate system. So that will give us an easy way to say this 0, 0 is the top left of our view and the size is the bottom right.
We're going to crop out the header label which we know is going to be this height because that what gets set up later. We're going to crop out the borders by insetting ourselves by two pixels, you know as shrinking the width by two pixels and insetting each side by a pixel basically.
And then we're just going to call setNeedsDisplay and rect our rect. So now we're never going to forcibly redraw the title or the size. We could be much smarter here and dirty individual lights as needed but that becomes much more complex and is not really worth time for this demo.
Now for you to run this again, you'll see that it's actually kind of ugly. There's a second line going on here and we never updated our drawRect. Remember our drawRect had this assumption earlier that the rect that gets passed in is our full bounds and we were just framing that.
So even though we made our dirty rect smarter, we actually haven't improved performance at all, in fact we've decreased it because we're doing these calculations now every time we need to call this and we're not really doing anything better. What we need to do is we need to change how our drawRect works. So here is where we're doing the frame.
And what we'll just do is say if we've got a rect around zero, we should outline our frame. That means that we know that whenever we're going to be dirtying a region, we're going to be dirtying a regions that's not starting out at 0, 0. So this is just a little cheat trick to say well OK if we get to 0, 0, then draw the full frame, otherwise, ignore it. Likewise for the title, we can draw that conditionally.
Unfortunately, we don't really know where the title is unless we've already created a title bar frame so we can't say that until after a title bar frame here, at which point we will call CGRect contains rect which is just an easy way to say does the rect that were being passed in contain the title bar frame rect? And now for you to run it again, you'll see that there is no extra or additional line here and that when the system is actually running, you can't see this but you have to take my word for it, it is no longer drawing the titles and the frames.
So very easy to actually do this, it gains a lot of performance benefit. This makes it actually work much better on the phone and that's that demo. But not everything is implemented here. What happens if we hit the "Check Reset" button. Remember that really awesome page curl animation we did earlier? That's not there yet.
So let's figure out how to do that. So first we need to talk how do you actually interact with the system. So if you want to use controls, use the built-in controls that we provide to you if you can. There are lots of them. The "Groups" button, the "+" button, the Slide Wheels, the SegmentedControl, the "Toolbar" buttons, there are tons of these and we provide them to you so use them. Don't subclass standard controls. So don't go, UISegmentedControl, it's awesome and it would be so much more awesome if it just did this. Don't do that because it's actually very hard to get that right.
Chances are that however you subclass it, isn't how we ever expected anyone to do that and then will break in a future release. Don't do it because the user sees a SegmentedControl, they expect it to behave like a SegmentedControl across the system. I know it's really tempting because it's sort of the way that you go, oh I really like the way it behaves here.
Try to redesign you app to be more cohesive not just with our apps and not just because we say it but because all your other fellow developers will also hopefully not be subclassing it themselves and so in that way you'll have cohesive behavior across all of the apps.
That doesn't mean that you can't have your own custom look. As you saw earlier, I was using a SegmentedControl for the actual run state thing here. Now by default, the SegmentedControl has a nice little blue look which looks awesome but really does not fit the panel on this machine.
So what's provided here is a tint color. It's provided on Toolbar, on Navigation Bar, on Search Bar, and on UISegmentedControl although on UISegmentedControl, it only works when the SegmentedControl is in toolbar style. So if you set the tint color, you can also clear it so that if you wanted to like use it to switch between states like red alert, red alert, you can set it and clear it by just passing nil and in this case I have this nice blue SegmentedControl. If I pass to the light gray color, I got something like this which matches much better with the look and feel of my app.
Use UIToolbars built in buttons, UIBar button system item provides a wealth of buttons that are already defined for you. Use them based on the text, based on their names not on the graphics. It could easily be the case that in a future system update, we decide this graphic would look much cooler for this button and much better fits the organized fill here and if by organize you really meant this is a folder, that will break for you in the future update. Using built-in graphics is also awesome because it lets you save time, it let's you be consistent with other apps and because we all know that time equals money, it let's you save money too.
So we've already created a bunch of nice graphics for you, feel free to use them. A bit about controls and how they actually interact. This isn't really going to be an event processing session but just in brief, control has an addTarget action for controlEvents selector on it. So if you had a "Dismiss" button like we do here and a target that you wanted the action to actually be done on, then given the controlEvents, now controlEvents is a must but let's just say we only care about UITouchUpInside.
If you called addTarget action for controlEvents and let's say the action is dismiss, then whenever a UITouchUpInside actually happens on this control, you will automatically get the dismiss call to your target. So controls are very easy to use if you want to write your custom controls for stuff like this. A lot of people then decide, OK, controls are really easy to use, I know how to do this, that's really-- that's a simple slide, how do I make my own really cool looking button? So let's say we wanted to have this nice little glowy speech bubble button.
Well you know that you can have like a UIImageView. That's a great way to display an image so maybe you'll subclass that with my button and then you'd say, OK well now I'll base this all on touches, 'cause a UIImageView is not in control and so you go all right, well how about a TouchDown or a TouchUp, then you'll realize, well what happens when the user touches down on it but then drags out because they really want to cancel that. Well you got to handle that.
What happens when they drag back in? What happens when a second finger goes down, do I start ignoring it? What happens when that second finger lifts? What happens when just the finger lifts? What if the finger drags off screen but that's not really far enough for my hysteresis to cancel. You really don't want to solve all these things. We've already had to solve it for you. So just use UIButton. UIButton has a very easy call, setImage:forState. This is was how I made those "Start," "Check Reset," and "Stop" buttons that you saw earlier.
You just pass in an image for the state and it's very easy to use. All right, so views, they're pretty boring, you know they are static things and control is OK, buttons on the screen. We-- what you really want to care about is animation, so how do animations work? Animations are done by a category on UIView called the UIViewAnimation category.
They are done without animators, what does this mean? If you're familiar with other, you know, styles of coding, you may be getting callback every single frame or as often as we can call you and say all right, where should this view be now, where should this view be now, where should this view be now? That allows you to do very complex animations but it also means that you have to do the calculations yourself every time and this is slow.
So because of this we actually don't use animators. The animations are actually handled by Core Animation. And because of that, Core Animation uses the hardware acceleration so the animations are very fast. Now, animations are pretty easy to do. You do a Begin, Commit and everything, right? There's something that often trips out a lot of people and I want to give a couple of slides here about this. The state is stacked.
So let's say you have a view and you call Set Frame on it, so you get your initial frame here, just like that. You then say begin animation, I'm going to be starting some animations now because you know that you need to move to a different state.
And then you call Set Frame call so now you're at a different place but then something happens, either the model changes or because you call that Set Frame something else jumps and you need to do it again and suddenly you have another Set Frame call and that leads to another Set Frame call and another Set Frame call.
This actually happens far more often than you might think. And then, all right, cool you're done. That actually is the final frame that you really, really wanted, not the initial one. And so you call commitAnimations. What happens at this point? The way that the animation system works is it just does a simple spanned animation between the previous state and the current state.
So although you may want it to go from the first frame that you set to the last one, what actually happens as it goes from the next to the last to the last one. And what actually does the user see, they see at the very beginning of your animation, your view jumps to wherever it was right before the end and then it animates very nicely from there to the end. And that's probably not what you want. So if you do need to have this case where you're setting frame multiple times, there is an easy way to work around that.
After begin animation, call setAnimationBeginsFromCurrentState. What this basically does is that it locks the stack so that the previous state or the state that it's in right now is what will be considered previous states. So when you call commit animation, it will actually animate nicely from your beginning to your end which is probably what you expected.
Note that this does not mean that it animates between each of these positions. It's just going to animate from the beginning to the end now, it's not going to go between stages. If you need to do that, you're going to need to either stack your animations with delegate callbacks or use Core Animation. So that's sort of like basic animation stuff. We also provide some very nice looking animations for you.
We provide the page curl animation, and we provide the flip animation so this was how the print view that I showed in the demo earlier actually works and it's very easy to use. You say UIView beginAnimations and I'm going to call this Curl Away My View. So in this example let's say that I'm just yanking this view completely off the screen and I want to reveal whatever is behind it.
We're not going to pass the context because we don't really need it. We're then going to call UIView setAnimationTran UIView animationTransitionCurlUp which is the definition for the page curl away animation. We're going to specify which view this is supposed to animate on and we're going to say yes to cache. We're going to set this duration to be 1 second.
We're going to set the delegate to ourselves and we're then going to commit the animations. Now outside of these animations box, so animation transitions are a little different. You don't really want to set the end state inside of the animation box. For other animation you're going to want to say beginAnimations and then move to the final state and then commitAnimations.
For animationTransitions, you really want to say just do this animation transition, don't do anything else while you're doing that, commit animation transition and then before the run loop returns so before the animation actually fires, that's when you're actually going to want to do whatever changes you need to do. So because we're yanking this view away, we're actually going to set this alpha to zero. Now why do we not just yank it to view, you know, completely out of the hierarchy at his point? If we do that then basically the animation itself won't actually run.
The animation will just be pulled out and the view will just disappear. So what we need to do is in the delegate, we're going to say myView removeFromSuperview so that's when we actually yank it out and we'll get something that looks very nice like this. So we provide this base, you know this built in pretty fancy animations, the page curls and the flips, but what if you wanted to do something a little bit crazier, like what if you wanted view to actually move along a nice little path.
So you can do that using Core Animation with your UIViews. So let's say we were given a CG path and we have a view that we know we're going to want to animate. The first thing we need to do is we need to generate a CA key frame animation.
We're going to say this is a position animation, we're going to set its path to be the path, we're going to set its delegate to be ourselves so that we know when it's done, some fill mode to frozen, removed on completion no because we want that state to remain in the system, and then finally we're going to call view layer addAnimation:keyFrame forKey position. Now remember earlier I was saying CALayer, don't go to CALayers, use UIViews for everything. This one case where you actually do need to know that UIViews are backed by a CALayer because you're actually applying this animation to the CALayer itself not to the UIView.
So this will provide a nice little pathway to animation. Another thing about UIViewAnimations is because of the way they're done without animator callbacks, what's going to happen is that you set up a delegate and you know your final callback, what-- you can change it or you can use the basic animationDidStop callback.
So you set up your animation, you commit your animation and then the hardware goes off and renders the animation and then eventually it returns back to you. So during this time, you're not going to be getting animator callbacks and so you may think, awesome, this is a great time to do a very heavy update on my model or calculate pi to 5000 digitssPtylY or in my case run the emulator a bunch because an animation is running and now I have time to run the emulator.
The case is this really doesn't work out well for you. Although, yes we're using the hardware to do all the rendering, it's a shared system bus, the-- you know, we need to shuttle things around. It still will slow down the animation if you're doing any work during the animation itself.
So the simplest thing is to say, don't do stuff here, I know this is really hard not to do, I am myself very guilty of constantly doing stuff here but just try not to do any stuff during an animation. So, in lieu of you know-- or sort of like on the same line as not doing stuff during animation, well the user might want to do stuff during the animation so going back to our first example of the Address Book where we have this "Groups" button and you click it and it slides over, what happens if the user clicks the button a second time? Are we supposed to move two groups back? Does your app handle that? Is it something that you expect at all, probably not.
And so during an animation, you want to stop handling all user interaction as well. So you can turn it off by calling UIApplication beginIgnoringInteractionEvents. Turn it back on in your delegate because there's nothing worse than seeing a really beautiful animation and then being stuck with an app that doesn't respond to your input. So now from here we'll have another demo.
This is how to actually do the printer curl. So as I showed you earlier, this "Check Reset" button really isn't doing anything. Awesome. So how do we make it actually do something? The first thing we need to do is we need to wire up the "Reset" button. [Murmuring] So, this app is fairly simply written, everything is actually done in the app delegate, a real app would have much more structure. But let's just say in the app delegate that we actually want to do something on the "Reset" button.
So the "Reset" button will automatically call reset pressed on the app delegate, and what we're going to do is we're going to call begin animations tear sheet, we're going to do the setAnimationTransition, UIView TransitionCurlUp, for view the printer view, superview. Why are we calling it on the superview? The printer view itself is actually that full width of paper, that 132 character width paper. You don't really want to curl that out.
What you're actually trying to curl out is what the user is currently seeing 'cause it makes no sense for the user to see a curl come, you know, already half completed as it-- by the time it comes on screen. We're going to set its duration to be 0.8. By default, the duration is going to be much faster than this and the page curl will look like you're really pissed off the printer so you want to slow it down a bit.
You're going to commit the animation and then you're actually going to clear the contents. This is what I said earlier about transition animations. They are a little bit different than your normal ones. And then we're actually going to reset the scroll point to the far right. This is just a nice [inaudible], a nice, you know, side to move anything over to the other side so that you're back at the first column of printing. So clear content currently doesn't do anything.
So we need to actually wire that up as well. The first thing we need to do is we need to add the actual call in our header file. Now the printer, the print view is really pretty simple. Basically it just stores the last eight lines because that's how much actually fits on screen and all we need to do when we need to clear it is we need to clear off those eight lines.
So we do that by calling clear content and then going through those eight lines and releasing them and setting them to nil, and then calling setNeedsDisplay. The printer will have to fully update. It cannot just update small dirty rect because for all you know, the lines cover the full width of the printer.
But we're just going to call setNeedsDisplay, we're not actually going to be calling drawRect here at all even though you know, I said earlier well you want to do everything that the animation needs to look like at the end now, you really don't need to. You need to call setNeedsDisplay and then drawRect will be called for you at the end of the run loop.
So for you to run this now and we get some content on the screen, and now you'll see it nicely tears off and continues printing and you can't it's clearing 'cause I'm running it but I were to stop it, you'll see it clears to a nice sheet of paper.
And because the emulator is actually still running during this animation, by the time that animation finishes running it's showing you the piece of paper that was underneath it. So as you can see that's really easy to do. So that was sort of a whirlwind tour of all these things. For more information, contact Matt Drance our Application Frameworks Evangelist. Documentation, go to the iPhone Application Programming Guide. It covers this and many, many other topics.