App Frameworks • iOS • 52:30
Come learn about all the amazing things you can do with Core Graphics. In this talk you learn from seasoned Apple engineers how to use Core Graphics to get great performance, beautiful graphics, and compelling animations. Learn to draw amazing custom user interface elements, customize the appearance of UIKit with procedurally generated images and animate between draw states. This talk will help you use Core Graphics to its full potential in your application.
Speaker: Bill Dudney
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript was generated using Whisper, it has known transcription errors. We are working on an improved version.
Today we're going to talk about practical drawing for iOS developers. So in my role at Apple, what I spend a lot of time doing is talking to developers about how they can make better apps, stronger apps, faster apps. And one of the things I often say to them is, "Hey, why don't you try drawing that with Core Graphics? I think you could do better.
Your app might be faster, might be smaller, and so forth." And the reaction I often get is, "Are you kidding? Can I draw that with Quartz? What are you talking about?" And so what I want to do over the next 60 minutes is talk to you about how at Apple we use Core Graphics to draw pieces of our applications, to make them beautiful.
So the first one I wanted to talk about is the Stocks app. I'm not sure how many of you have taken your Stocks app when you have the graph showing on the bottom and turned it sideways and seen this interactive graphic. But this is fantastic. There's no way that we could ship all of the possible images that would fit in there for every possible closing price. So we have to draw that at runtime. We use dynamic data that we download off the internet, and then we draw it with Core Graphics.
Another one is weather. So the images like the sun that's got a little bit of cloud on it and so forth, those images are pre-rendered. But we draw those into this beautiful looking table view at runtime. The next one is numbers. Of course you can put data yourself into numbers. Hopefully your weight loss progress looks like that.
Mine often looks the other way. But users type this data in, so we can't draw this beforehand, we have to draw it at runtime. Next is in a lot of our table view cells to make them really fast, like the YouTube application here on iPad, we draw that with Quartz.
Next, this is one of my favorite pages, has this beautiful rendered ruler at the top of every page. Now if you look closely you'll notice in portrait and landscape we have a different ruler there. One is demarked in eighth inch increments and the other one in sixteenth inch. Now of course we could have put two different pings in there and we could ship with both of those pings and then just switch between them. But it makes the application smaller to draw that again with Quartz.
Next up is iBooks. I love this application. There's so many cool things available and it's just so beautiful. I don't know how many of you ever looked. I tried to get a screenshot of this, but if you tap on your bookshelf here and you pull down, there's a nice embossed Apple logo in the background. It's just beautiful. All this fit and finish and this background and all the things you see on the screen are of course drawn with Quartz.
And then next is GarageBand. I love this app. I don't know if you've played with this feature, but there's a whole bunch of samples built in that you can play. My kids bark into the microphone and then play themselves barking back at each other. It's a fantastic app. But the sound graph that you see on the top right hand side there, that's drawn again with Core Graphics.
So let's look at the agenda of what we're going to do today. What I wanted to do was take you through how to draw something that looks very similar to this. Last year we did a talk and one of the engineers at that talk went to the labs and he was inundated with questions about how this got drawn. Someone told that he drew this himself, that he wrote the code. So people just mobbed him. So I thought this year what we do is walk through drawing something like that.
To see how it's drawn though, let's break it down into individual layers. So first of all we start with the gradient background. Now of course we can't have square edges, everything has to be rounded and safe for children, right? So we want to round the edges off. We'll talk about how to build clip pads that look like that.
We also want to draw a data grid. Now on the bottom is where we'll draw the volume data, how many shares traded hands in a particular day. And on the top part of that is where we will draw the closing data that's got the waves in it and so forth.
On the bottom we have room to put text, and on the right we have room to put labels for these lines. So the clipped horizontal lines show how much, what the value of the stock was on any given closing day. And then also to really make it stand out we draw this line pattern underneath it. underneath it.
Finally, we draw the closing data over the top of those two clipped groups of shapes, and then the volume data down at the bottom. and also the text labels at the bottom, we're going to draw those and I don't know how well it shows up on the screen here, but there's a nice shadow behind those. So we'll look at how to do that as well. So simple stocks. That's what we're going to go through in the next 60 minutes. And when you leave here, you're going to know how to do everything that you see in this graphic.
So let's get started by just looking at a couple of simple examples of how drawing gets done. So again, I spend a lot of time with developers and they bring me their code that they've downloaded off of the internet and say, "I look at this and this looks so simple, but when I try to do it myself, it doesn't seem quite that simple." The API that we use behind that that makes drawing work is Quartz. And I've already said Quartz several times.
Behind that is Core Graphics or the same thing as that. So I might say Quartz and mean Core Graphics and say Core Graphics and mean Quartz. It's the marketing term. And you know it's the marketing term because it has this beautiful Q graphic. Programmers don't make art like that.
Okay, so when the developers bring me their code and show it to me, oftentimes they've downloaded something like this, where it's a really simple example where we do color fill. And what you see is someone says override the draw rect method on your custom implementation of view. Set red or whatever color to be your fill color and then call this UI Rect Fill. Now of course this works and that's exactly how I got the screenshot to show to you on screen here.
But who wants to write code when you could click one button in Interface Builder, right? How many of you on your very first iPhone project changed the color of your flashlight from white to red? We all did that, right? So this works but it's not a great example of using Quartz because one, it's inefficient and two, there's much better ways to do that with Interface Builder. So let's talk about an example that actually requires that we dive down into the Quartz API. So we're going to build this beautiful gray gradient. Start with white on the bottom and fade into this little bit of a darker gray at the top.
And here's the code. It's five lines of code. Deceptively simple, right? It's really easy to use once you get your head wrapped around it. But you might notice that there's some parentheses instead of square brackets. And so it is a CAPI, but I don't want you to panic. It's really straightforward to use once you get to know it.
Bill Dudney So it's just objects, and instead of the object being on the left-hand side with a square bracket in front of it, you have the thing you want done, and then you pass in the object and then the arguments for it. So it's very straightforward once you get your head wrapped around it, and that's what we'll do in the next little while. But UIKit also covers a big chunk of the Core Graphics API so that you don't ever really have to think in terms of the CAPI. So let's go through the code one line at a time. Thank you.
The first thing we want to do is grab the graphics context from the UI Graphics Get Current Context method. Then we create the gradient. Now I'm not showing you how to create the gradient yet, but we will see that in just a couple of slides. I wanted to put it behind a method because it's important that we cache these objects that Core Graphics uses so that we can reuse them again and again because when we do that, that allows Quartz to take advantage of a lot of caching that it does on the back end bookkeeping for what the gradient looks like and so forth.
Bill Dudney Then we specify the start and end point and in this case we're going to draw a linear gradient so we just set a beginning point and an end point and then we're going to have that gradient build across that line. And then it's one function call. CG context, draw linear gradient. The first argument is the object that you want to act on and then the remainder of the arguments are just the parameters that you're passing into essentially that method call.
I told you it was easy. Do you believe me? Okay, so building a gradient. The first thing we want to do is set up the colors. So these are the individual colors, the white color, the medium gray, and then the little bit darker gray. Then we set up the color stops and these are the locations along that line where the color is going to reach the particular color that we specified.
We're going to grab the device gray color space because in this case we're drawing a gray gradient. Then we create the gradient. Now we're done. We've created our gradient and we can use it to draw. We do a little bit of memory cleanup there by just releasing that color space that we created.
So now we've seen a real example of something that we actually need to use the Core Graphics API for. So let's take a step back and take a look at the basics and walk through how drawing works and look at the drawing model so that when it comes time for you to draw you'll understand how to get what you want onto the screen. So if you've been to many of our talks that have talked about any sort of graphics context inside iOS, you know that the top left hand side is the origin. X is going to the right and Y positive is going down.
It's a geometric system in which you describe what you want drawn. So you can make statements like, "I'd like to draw a circle," or "I'd like to draw a triangle," or a square, or something even more interesting like this double-headed triangle, or a double-headed arrow. or even really interesting stuff like this.
So all of these are just individual things called paths that have been drawn using particular colors and you end up with something really beautiful that looks like that. I don't even know what that data represents but it looks cool. Or you can take the closing data that we're going to download off the internet and come up with a graphic that looks like this.
So to talk about what points are, points are the individual sort of the atomic bit of how you describe what you want drawn. They're abstract. They're a place in the space that you're going to draw in. There's no concept of resolution on a point. So Quartz doesn't think or it doesn't present to you an API that thinks in terms of dots per inch. An important point to keep in mind is a point is at the intersection of two pieces of your number line, your X and your Y, just like you learned in your geometry class.
On the flip side though, of course, pixels are concrete. They're physical. They're either there and on or they're off. And if they're on, they have one color value. They can't have multiple color values. And so what Core Graphics does for us is it does the translation between those two things.
So we describe in a very abstract way, we say, "Hey, I would like to have a line width of two points, and I would like to have the first point be here and the second point I want to make a line to." And that's what we end up with.
On the left-hand side, we see we're filling eight points because our line is two points wide and it goes across four points. But if we were to push that to a retina display where one point is equal to two pixels, we would cover all 32 of those pixels.
But it's really important that instead of thinking in terms of pixels that we think geometrically. A 10 point system font is the right point size, not 10 pixels. 44 by 44 points is how big you should make your touch areas. When the iPhone 4 came out and we started talking about retina display we really stressed this.
If you made a touch point that was 22 pixels by 22 pixels, or 44 pixels by 44 pixels, people wouldn't be able to tap it. It would be really hard to get their finger on it. And then of course all of the devices that we shipped, iPhones, iPod touches and so forth are all 320 by 480 points.
So we've talked a little bit about context and what we're doing with the context. Let's take a step back and talk about what we do with the context or how a context is put together. It has a list of properties and then those properties are used at draw time to figure out what pixels need to be filled and how they should be filled.
One piece of those, of that property, properties is called a path. And so here we're creating a rectangle path. So we're asking the context, please add a rectangle to your state that's a path. And so that would build this path. Now remember it's abstract, that's why it's got dots around it.
Then I can set the fill color to some blue color. And it's kind of, you can think about it like you're telling your kid what color crayon to pick up, and then when you ask the context to draw, it's going to fill with that crayon that you asked it to pick up.
So here we have our rectangle that's filled with that blue color. It starts at zero, zero, and it's 400 points by 400 points. I could also with the UIKit cover API that sits over Core Graphics just call UIColor blue color set fill. Now that blue color becomes the fill color and then I can call the UI graphics function UIRECT fill with the CG RECT and it would fill that RECT.
And again it does exactly the same thing but this is using perhaps a more convenient or more familiar API. Another piece of the context, or a bit of the information in the context, is called the Current Transform Matrix, also called the CTM. This is the definition of user space. It's what defines where points lie, how big they are, how far apart they are, and so forth.
I can do things like translate that CTM and then reuse the path again and draw it again and it will draw it in what looks like a different location. So here I'm translating the CTM in the X direction by a positive 100 points. So it moved my square from 00 to 100, 100 but the rectangle that I drew still started at 00.
Context lives in what's called a stack, a graphic state stack, and I can push copies of it onto a stack, make changes, and then when I pop that copy off, all those changes that I made are discarded. It's very convenient and it becomes very important when we take a look at something, some of the code that we use to draw the stock graph.
So the first thing we do is call saveGstate and that pushes a copy of the context onto the stack. Then we do some translation, translate 100 to the right or left or however other we wanted to translate it. And then all the drawing that we do from that point forward lives in that translated user space.
So we've changed the definition of what a point is and where they lie and so forth. And so all the drawing that we do from that point forward is affected by those changes. Then when we call restore G state, all of our changes are thrown away and we're back to the context that we had before we called save G state.
So really important point about context. You can't call us. We have to call you. I see code like this a lot trying to help people understand why their drawing doesn't work. They have some event processing code, the user's tapping on something and they want it to change from red to green or whatever. In their event processing code they're trying to draw. So they call UI Graphics Get Current Context inside their tap gesture recognizer code. Well of course it's not going to work there and the reason is that there's no context.
It is really important that when you have user interaction that you're updating state. So in this method, instead of trying to draw, what you want to do is update your state, make whatever state changes happen because the user tapped or panned or whatever, and then tell the view that it needs to display itself. Calling this method is really inexpensive. It just flips a single bit on the view and marks it that it needs to be redrawn.
So you can call it as often as you want. Now the beauty of this is you can mark a whole lot of views needing to be displayed or the same view needing to be displayed many times and you won't actually trigger any drawing until it's time. And we keep track of that inside UIKit. And when it's time to draw, we'll set up a context and invoke your view's drawRect method.
Inside drawRect, we have done all the work to set up a context and we've set that to be the active context. So when drawRect happens, you're able to draw. So when you call UIGraphics.getContext here, of course, there's going to be a context because we set it up for you.
Now of course it wouldn't be a great rule if it didn't have an exception. And we understand that there are cases where you're going to want to draw when something interesting happens. And so what you can do is call UIGraphicsBeginImageContextWithOptions. And that does all the heavy lifting to put together a context and make it the active context. Then when we do things like use UIKit drawing methods like UIImagesDrawInRect method, it has a current context so when it draws it's going to go into that current context.
Then I can ask the current context, hey, give me the image. Now I have my scaled image, which is what I'm doing in this method.
[Transcript missing]
So if you've spent any time in the API documentation for Core Graphics, you've probably seen the CG bitmap context create. Or if you've spent any time searching on the internet for how to draw in the background, you've probably encountered this. Now it's really feature rich. There's a lot of things you can do to create a CG bitmap context.
On the flip side, you can use the really simple UI graphics begin image context with options and all you have to do is pass in the size, how big do you want the context to be, what opacity, do you want it to have opacity or not, is it completely opaque, and then you set the scale. And you can even pass in zero there and it will set the scale appropriate for whatever piece of hardware you're running on. So two different approaches.
The way you decide which one you want to choose is based on whether or not you have some custom algorithm that needs access to the backing data, to the actual bits that make up the bitmap that you're going to build. That would be a case where you'd want to use your own bitmap context that you build with CG bitmap context.
In virtually every other case you're going to want to use the UI Graphics Begin Image Context. We make sure that that's optimized for displaying on the screen and then we have other functions that will pull a ping file or a JPEG out of there. So if you want to email it or whatever you can do that. So that's a wrap up, or that's the extent of the basics.
The context is a geometric space in which you're going to draw. You should think geometrically. Core Graphics translates that geometric description into some pixels. And the great thing is we never have to worry about peaking and poking video memory. Hooray for that. The current transform matrix defines how points live, how they relate to each other, what the current space is. And then last and most important about this initial section is you cannot just arbitrarily start drawing anywhere. You have to have an active context.
So the primitive of how you draw things in Core Graphics is a path. And a path is made up of one of four pieces: a point, a line, an arc, or a curve. You create a path with UI Bezier Path, call the class method Bezier Path, that's going to return an auto-released object to you.
Then you create a CG point and you tell it move to point. Anytime you call move to point that's going to create or begin a new path, but that point is kind of lonely and he needs a friend, so we're going to set up another point and call add line to point. So now I've built this geometric abstraction which is the line that goes from the initial point to the second point.
When I set the line width to 1 and then ask that path to stroke, it's going to draw into the current context, it's going to be one point wide, and it's going to go across those three points long. Now, this is the abstract geometric description for what would happen.
However, we know that pixels are not abstract, they're hardware, and hardware is not nearly as malleable as software is, right? On a high retina or a high DPI display when we drew that, it would look great because there's twice as many pixels as there are points.
[Transcript missing]
One other piece of information where you have to think about that kind of stuff. So here we have a point or a line that's going between these two points.
And we've set its width to be one wide. If we want a one point wide line to align exactly with pixels, And not overdraw like this. What we need to do is offset its point value by .5. So that's any time that we have an odd width on our, on the line width, any time you have an odd value there, you're going to want to push the line .5 one way or the other, right or left, or up or down.
But of course there are many, many cases where it doesn't matter how hard you work, you're never going to get the thing you have described to line up with pixels. So, I'm going to get back on my soapbox now and tell you to think geometrically. Unless in those rare cases where you need to be pixel perfect. Alright, so enough about points and lines. The next piece is arcs, and arcs are exactly what you learned about in geometry. You have a center point, you have some angle, and a radius, and that defines where the arc lies.
You also have two types of curves. The one that's illustrated here is called a Bezier curve. It has two endpoints. So you call move to point to begin the curve and then you call this add curve to point control point one and control point two. And those two control points define the tangent of the curve at the two endpoints.
So as you draw these paths, eventually you will probably want to close them so that they are a complete geometric package that you can then do interesting things with. So the way you do that, you create a path, you call move to point, then you call add line to point, you might draw add line to point again. Then you'd call closed path. Now we have a complete geometric path that we can then pass around and we can maybe be able to fill with that or we could use it to clip with like we saw with the rounded rectangles and so forth.
Other interesting paths. Here I'm creating an arc that goes all the way around a circle and then I call fill and it gets filled. Now again, Core Graphics is going to worry about what pixels get filled. I just think geometrically. So another great thing that we can do with paths. Once we have a path closed, we can use it to clip with. So here's an image and we want to crop it and we want to put some rounded rectangle edges on it so it just looks perfect, beautiful.
To do that, we specify the rectangle that we want to pull out of the image, the cropping rectangle. Then we create a UI Bezier path telling it how big the rectangle is, which corners of that rectangle we want rounded, and then what radius we want to use to round each of those corners.
Then when we add that path as the clip path, and the clip path is another piece of state, it's another property on the context, when we add that to the context, no pixels that fall outside of that will be affected by any drawing commands until we remove that clip path.
So when we call image draw at point or draw in rect, it will clip that image inside our rounded rectangle and we'll end up with this image. That's a much more efficient way to approach drawing images with rounded corners than it is to use layers rounded corners, CA layers rounded corners. Let's go to the demo.
Okay, so again, remember in our demo what we're going to do, there's seven steps and they're outlined here in each of these comments. We're going to draw the gradient, we're going to round the corners, we're going to put the grid, we're going to put the horizontal grid, then we're going to draw that line pattern, then we're going to draw the closing data, and then the volume data, and then finally the month names. So let's start, make sure my demo script is set up properly, let's start with drawing the background gradient.
So the code is straightforward. This is exactly what we saw on the slide earlier. We happen to be using a colored gradient instead of a black and white gradient. But we grab the context, we set the start point, we set the end point, and then we ask the context to draw the gradient. The gradient that we're using is hidden behind this collapsed code just above here. We'll look at it in a second. We just grab the gradient, pass that off, and ask the context to draw that.
Just to prove I've got nothing up my sleeve, we'll go ahead and look at that code. And it's the same thing, but in this case we're using an RGB color space because we want to have a blue gradient. And so we're setting up those colors with all three of their color components.
We have four color stops, and that's how we get that sort of hard break in the middle. So we set up those four color stops and that's what makes that look like that. It happens to also be a universal application. I wanted it to run in the iPhone, so let's do that. So now we have our blue gradient, but we're missing the rounded corners.
So just like again we saw on the slides, we take a rectangle, we tell it which corners we want to be rounded, we tell it the radius to round, and then we add that to be the clip path. Now none of the drawing that we do is going to fall outside of that path that we just set up.
So now we have our nice rounded corners. The next step of course is to draw our vertical grid. And I drew this into a ping file so that we could talk about it for a second because there's some detail here that I don't want to look at in code because it's too much code.
So if we notice here we have the three vertical lines that represent each of the months in the data that we're going to draw. So the one on the far left hand side, the next one, and the next one. So each of those represent a month. On the far right hand side we have sort of the end cap. That marks the area in which to the right we're going to have labels for each of the prices that we have. On the bottom we have two horizontal lines that contain where our volume data is going to be.
So remember we talked about translating the CTM. So what we're going to do is set up a path that's going to represent each of these lines and then we're going to translate the CTM and reuse that path and draw the same exact lines. So we're going to do that path four times in a row by just translating the CTM. So let's go make that happen.
So the first step is just to set the color, and I sampled this color off of a screenshot. You'd probably have an artist tell you what color to put there. And then I tell it to set itself as the stroke color. Next, I'm creating the line that I'm going to use to draw the vertical lines. And remember, we're just going to use one path. And this path, since we're reusing it, it allows Core Graphics again to take advantage of all the caching and bookkeeping that it does to make this path draw the first time.
The next step is to do a little bit of bookkeeping. And I have, again, all of this code is available in the demo. And this is just doing some math based on the data set that we have. The trading day line spacing. Remember that in every trading month there's somewhere between 19 and 24 trading days per month and we want the lines for each of our months to fall in the appropriate spot. And so that trading space per day tells us how much space we have in each day. Then we have another calculation that we use to calculate how many trading days we're in a particular month. And so that's what we're using that for.
The next step is to grab the graphics context and save the state. And again we're saving the state because we're going to use Translate and we want to be able to get back to the current context. So now for each of our sorted months, so in our case here March, April, and May, we're going to go through, figure out how many trading days were in that month in our data set, and use that to calculate how far to the right we want to move before we draw the line.
Excuse me, then we draw our line once, we move over the appropriate amount, we draw the line again, we move over the appropriate amount, and we draw the line again. So that's the first three lines for March, April, and May. Does it make sense? Now we've translated the CTM so we want to make sure to restore it. So now we're back to the context that we started in.
So the next step is going to be to draw that end cap, the last line on the right hand side. So we're going to move all the way to the right, to the end of the data set that we have and draw that line one more time. So now that we have all of our vertical lines, it's time to draw the box that we're going to put the volume data into.
And that's basically, the approach to that is exactly the same. We're going to create one path that's going to go across the entire set that we want. We're going to draw that and then we're going to translate in the Y direction this time and draw it one more time.
And there we have our grid. So this is the place in which we're going to draw the remaining pieces of our stock graph. So now that we have the grid, the next step is to go and draw in the vertical grid. Sorry, I just drew the vertical grid, the horizontal grid.
Getting ahead of myself. So the horizontal grid is going to be each of those dotted lines that shows us where the stock price is. And the approach to that is going to be very similar, so I'm not going to spend as much time going through that code. In my case, I chose to draw five lines. You might need to draw seven or two or whatever for your data set, but in my case I chose to draw five lines.
Again, they start on the left and they go all the way to the right of that rectangle that we want to draw the data in. Now one interesting piece here that we didn't cover in the slides is this dash pattern. So this is specifying one point on, one point off, and set that as the dash pattern for the line. So anytime that this line is drawn, it's going to have one point on and one point off.
So pretty close. Now we have the lines that we need, but they're not clipped to our data, right? And it's really important that it gets clipped to the data because that's what gives it that beautiful detail of one pattern on the top and a different pattern on the top. I mean, one on the bottom and one on the top.
So let's look at how we do that. And you can probably guess that what we need to do is save the context state and get a clipping path. Now the clipping path that we're drawing here though is I'm hiding it behind this method, top clip path from data in rect. The way I created that was took the path that I create for all of my closing data where the stock value closed on each day.
That creates one path. Then I start on the right hand side at the end of that data set. I go up to the top of my rectangle. I go all the way back to the left down to the first data point in my data set and then close the path.
That strangely shaped path then becomes my clipping path. So any drawing that happens that's outside of that area is going to be clipped and not shown on the screen. So now when I draw, I get my horizontal lines, they're drawn in, and you can see that they're clipped. But we still can't see where the data lies. So let's go draw that nice line pattern underneath.
And the approach here is going to be very similar to what we've done already. We're going to draw some lines, translate the context, draw some lines again. Oops, very important point I forgot to do. I told you guys never to forget to do that and then I forgot to do it.
Since I added that clipping path to my horizontal grid and I forgot to restore the context, now the top clipping path is set up. And if I had proceeded with writing code and clipped to the bottom, we would see no new drawing and then I would have panicked because I would have not been able to see what was going on. So let's go add that down here.
Now we're back in a consistent state. So actually a great learning opportunity. If you're drawing stuff and you're setting up clipping paths and you see zero, nothing on the screen, it's probably because you've done something weird with your clipping path and you're not allowing anything to be drawn.
And a great debugging tip, what I do when I'm in that situation is I take that path that I'm using as the clip path and I draw it. Set my line width to be five, set the color to be black, and just draw that path. And then at least I can see what in the world it's clipping to. So, debugging tips. Alright, draw pattern under closing path.
So the first thing we want to do is grab the context and then create our path. And in this case we're setting the line width to be one. Whenever we have an odd width we want to make sure that we offset .5. If we wanted to fall right on a point boundary we want to offset by .5 so that's what we're doing here.
Then the next piece is to grab this alpha step. So we want to start with our alpha set to .8 and we want it to fade down to our alpha being .2. So we start at .8 and we come up with a step. How much should we decrement the alpha value essentially on each step? And that's the math to do that.
Next we want to save the graphic state, create our line, figure out how far we want to translate the CTM in the Y direction each time, and then update the stroke color with every time changing the alpha value to be a little bit more clear, a little bit more clear, and so forth. And now if I draw this, you can probably guess this too, it would draw over the entire data rect, which is not what we want. We only want it to fall below.
So let's grab the context and then in this time we're going to get the bottom clip path which does exactly the same thing that the top clip path does except it's flipped. So I go to the end of my data set, I go down to the corner, back all the way to the left, and then up to the beginning of my data set and clip to inside of that path.
So, this method is deceptively simple looking, right? This is just grabbing the path for our closing data and then setting the stroke color to white and then drawing it. So barring the line pattern underneath that I've totally messed up and can't make work, I apologize, the sample code works. So now we see our closing data showing up. The next step is to draw the volume data across the bottom.
One thing I wanted to show though before we move on to drawing the volume data. So here's the method that's going to create that path for the data. Now it looks like a lot of code but really all it's doing is scaling, taking all of my closing data and scaling it to fit within that rectangle that I have to draw it in.
So it's really just a whole bunch of different math to scale it properly and then it's calling move to point, line to point, line to point, line to point, line to point for each one of those things. So it really is pretty simple. It's just a bit of math to make it all fit together.
So the first thing we want to do on drawing the volume data, now remember for every trading day we're going to have some volume amount so we want to move along by that trading day spacing that we saw earlier and we want to draw a line from the maximum value down to the bottom of our rectangle. So this is doing all the math to set us up to be able to do that and then we set white to be the stroke color.
The next step is to iterate through all of that data and then create a new path for each one of these lines that we want to draw. Now in this case we can't use the translate trick here to reuse the path again and again because each of those paths are a different height. So we're going to create a path, set its two endpoints, and then draw the path and then release the path.
And now we see the volume data. So that's it for this piece. The next thing is to go back and talk about drawing text. So because I explained to you how to debug it, it went south on stage. During my practice things I never told anybody about how to debug that problem. So it's the demo monkey.
I forgot to sacrifice a goat or something. Okay, so demo wrap up. So we learned how to draw these lines. We learned how to apply a clipping path with some group of data. And we also talked about how to get crisp lines. Remember whenever you have an odd width, if you want it to be crisp, you need to move it 0.5 one way or the other.
So text drawing, there's two ways that you can approach that. In UIKit there's a bunch of extensions to the NSString class that allow you to find out interesting information like how should I lay this text out and so forth, and then draw the text. But if you have serious text needs, you need to look at Core Text. It's a full featured layout engine, there's tons and tons of great features, and it will draw paragraphs and a bunch of different fonts and so forth, it's really cool. We're not gonna talk about Core Text, we're gonna talk about the UIKit approach.
So you can do things like give me the size with font. So you pass in the font and the NSString will return to you a size that it will fit in. Knowing that size you can then use that information to lay that text out. If you have multi-line text, you can tell it a rectangle that you want it to draw in and it will return to you a size in which it thinks it can fit all of that text.
Once you know that, you can calculate this point and tell it to draw at that point. And the same thing for a bigger group of text. Once you know the information, you would calculate your text recs and then have it draw. So again, I just want to drive this home one more time. You cannot draw strings in your tap gesture recognizer code. You have to have a context, and in the event processing case, you don't have a context yet. Instead, tell it you need to display.
If you want to know more about Core Text, last year we had this great talk called Advanced Text Handling for iPhone OS. There's some sample code that will actually draw the Constitution and a bunch of other stuff in great interesting fonts. So go grab that at ADC on iTunes.
Bill Dudney Okay, now we've seen how to draw the text across the bottom, but of course we also want some nice shadows there. Shadow state is another piece of information on the context. So we set that up by setting up a shadow, telling it its offset, color, and blur radius.
But it's really important that we use this GState call because once we set the shadow, all drawing from that point forward will use that same shadow. So we want to set it, draw our shadowed stuff, and then call restore context. so that we go back to non-shadow drawing.
So this is how we would set up some text that we want to draw with a shadow. We set our shadow height to two, we're going to set our shadow color to dark gray, or actually in this case because it's hard to see dark gray I set it to orange. I asked it to draw and then it shows up with a shadow. And then I call restoreGState and any further drawing that happens isn't going to have that shadow.
So I want to take a step back and talk about this painter's algorithm which is the way that Quartz draws. And it's like when you paint your house. When you paint with red color and you decide you don't like red and you paint with blue, the color that you see is blue.
Quartz uses that same approach. Now of course it takes into account alpha and it will do blending and so forth, but the general idea is that whatever the last color to go down is what wins. So if you were to draw these three circles with three different colors, it would look like this. And since the red was the last thing to be drawn, that's the thing that you see on top.
In the shadow case, and this is something that a lot of people get wrong and they don't know how to do it, which is why I wanted to make sure and put it in here. Our sample won't actually run into this problem, but it's a really common thing.
We set the shadow, we draw those three circles, and the shadows look nothing like what we wanted them to look like. The shadows overlay. So remember the shadow state sort of lives with the context, and any drawing that you tell the context to do is going to have that shadow applied. So since these three shapes were drawn separately, they each get their own shadow, so those shadows overlay.
So if that's not the effect that you want, if you want all of them to be together and to end up drawing with one shadow, you would use this thing called a transparency layer. It's sort of like a grouping thing. It says take some interesting pieces of the context state like shadows and only apply them after all the drawing is done. Okay, so let's go back to the demo machine one more time, or real quick, to look at text drawing.
So we want our month names to be localized. And I don't have time to go into all the detail about how localization works. Again, we've had a lot of great talks about that. But you can't just assume January, February, March. Some calendars have 13 months, some have 13 some years, and 12 others months. It's really complicated. Who wants to keep all that stuff in their head? We've written it down into something called NS Calendar. Use it. And that's what this code does. So go take a look at the sample code and then some old WWDC stuff.
So we do that setup stuff. And then remember that we want each of these month names to fall under these lines. So we have one line for each of our month. So we want those names to fall in those locations. So we're using our daily trading space calculation again to find out how far over March, April, and May should fall. So we do those calculations. We figure out how wide the month is, what the name of the month is, how wide it is, then we lay it out.
"And now we have our text labels across the bottom, but there's no shadows yet. So let's go do that." We're going to do that right here after we save our graphic state. And I don't have an additional graphic state saved here so I don't have to add an additional restore at the bottom.
So now when I run, I have the nice shadows at the bottom of my text. So we're almost there. And actually, we are done with what I showed you in the agenda, but I have one extra piece because we have six and a half minutes left that I want to show you that I think is really fun. So let's go back over to slides.
So we're almost there. Images. UIKit can draw images. And I want you to feel free to draw images, but UI Image View is much more efficient than drawing your own images this way. So if you have images and you want to put the full image on screen, use a UI Image View.
If you must draw an image, you can use Draw at Point and Draw in Rect. Bill Dudney Now in the sample I spilled this earlier and I showed the code, I didn't want to, so I want you guys to download it and play with it. There's a method in there that will pull this beach thing and draw it inside a clipping path.
So feel free to go after it and mess with that. Earlier today there was Understanding UI Kit Rendering, which talks in depth about why using this approach is less efficient than using a UI Image View. Bill Dudney Okay, so the last piece, I love this stuff. It's sort of ugly, but I love it.
It's ugly because I drew it. If I had an artist, it would be awesome. So a pattern is a single image that's drawn repeatedly to fill some space. So in this case, I used Core Graphics to draw this really simple 48 by 48 image. And it's just a radial gradient with two lines across the corner. And then I call UIColor Color with Pattern, and that pattern image becomes then sort of a fill color that I can just fill this thing with. And so I set the background color to be that pattern.
and that's how I ended up with the background. But don't be afraid to do this in Draw Rect. I know earlier I sort of said, "Look, if you can set the background color, set the background color." But you can also use patterns in Draw Rect and they can be really efficient as well.
So as homework, in addition to putting the beach picture behind your stock graph, I also want you to take that 48 by 48 pattern and use that to stroke the stock path. It looks pretty, I don't know if cool is the right word, but it looks interesting. Okay, so I want to show you a demo of using patterns, and I'm not sure how well it's going to work out because it's supposed to show up in the bottom, but we'll give it a shot.
Okay, so the pattern stuff is going to work. It's going to be magical, you'll love it. Okay, so I grab the image, I set it to be the color, and then I'm getting that bottom clip path, which is again my stock data and down and right and left or whatever, and then I take that path and I fill it with my pattern color.
So it's kind of ugly, but it's still pretty cool, right? I took this 48 by 48 image that I created with Quartz and I stuck it in the background. Now, or I stuck it inside this sort of really cool path. Now, observant, if you're really observant, you'll notice that that pattern is clipping half of our path.
So remember how I talked to you how a path is going to draw half of its stroke on the top and half on the bottom or half to the right or half to the left? Since we drew this pattern after we drew our path, we're going to draw half of its stroke on the top and half on the bottom.
So if we want to fix that, we take advantage of our knowledge of the painter's algorithm and we go back down here to draw rect. And we grab the line that's drawing that and we put that above our closing data. And now since the pattern gets drawn first, we see the full width of our path. So that's the end of the demo. Whoever sacrificed the goat, thank you very much.
So now when you download the sample code you'll be able to draw this and that's the full thing. So in summary, you can draw that with Quartz and I cannot wait to see the amazingly cool stuff that you guys go and do with this knowledge when you get out there after the beer bash. So draw in context. You have to have a context. You can't do it without a context and think geometrically. So finally, if you have any questions, feel free to send me an email. There's documentation. Please say hi.