Core Graphics isn’t scary, honest!

For anyone who’s developed exclusively with UIViews on iOS may take the title of this post a bit oddly. “WHAT?!” they might say, “Are you insane? Core Graphics is not only a C-only API, but has confusing function names, and needs way more code to do the same thing I can do in less code in UIView”.  Yes, they might be right, but there’s a reason why Core Graphics exists. It’s FAST!

But using Core Graphics doesn’t mean that your code has to be confusing, or that you have to compromise flexibility for performance. You can have your cake and eat it too (aka you can have high-performing code that is easy to read). Read on to see what I mean.

Baby steps in using drawRect:

The drawRect: method is your entry point to drawing in Core Graphics, and is (for the most part) the only place where drawing operations can be performed. This is the biggest problem newbies have when getting started with low-level drawing on iOS because they’re used to being able to add subViews or subLayers arbitrarily in whatever method they happen to be in.

drawRect: is called by the underlying graphics subsystem in its main frame rendering loop.  When it’s time to draw the next frame, iOS digs through all the views on the screen and checks to see if its content needs to be updated. If it does, then your drawRect: method is called with the appropriate rect that needs to be updated.  It’s as simple as that.  You get asked to draw your content when, and only when, it needs something from you.

Finding your context

All drawing operations in Core Graphics utilize a context pointer to keep track of what region your drawing operations will be made to. This is a standard thing you’ll find in C APIs because without an Object-Oriented “self” property easily available, you have to provide some means of providing context.

From that point on you can use the “ctx” variable for all subsequent Core Graphics calls.

Saving state

If you want to draw anything on the screen, even if it’s something as simple as a single line, you’ll need to use several function calls to do so. This is because separate calls are used to set attributes such as the line colour, the line width, any end-cap settings you may want for the corners of your line, and then the actual path that your line will follow.  Core Graphics keeps track of all these different attributes by updating its internal state.

Think of the drawing state as a pen: you change the tip, you change the colour, and then you can draw many separate lines with the same pen.  But if you forget to change your pen back, you can mess future drawing calls up if you don’t clean up after yourself.

To work around problems like this it’s best to use the CGContextSaveGState and CGContextRestoreGState functions.  Think of it as saving a bookmark or a checkpoint of your current graphics state.  You can then change any drawing attributes you like, you can draw or fill or stroke any path, and when you’re done you reset the graphics state back to what it was before.

There is one important catch however: the number of times you call Save and Restore must match perfectly, because otherwise you may screw up the drawing state for whomever is calling your class. This is important because otherwise some really weird garbage may end up getting drawn to your screen.  To get around this, I use a very simple technique that I’ll show in the code sample below.

By including a new set of brackets in your code, you give yourself a visual indentation of code that is covered by a set of save and restore calls.

Simple drawing using UIView classes

There are some fairly simple operations that some people want to do that are made easy by a few simple helper methods provided in a few UIView subclasses. For example, assume you’re building the next greatest Twitter application and you want to squeeze as much performance out of your feed items as you possibly can.  Obviously rendering text, images, gradients, borders and shadows are fairly expensive operations if you want to handle scrolling through thousands of feed posts at 60fps.

Fortunately you don’t have to replace your UIView classes with hundreds of lines of Core Graphics calls. Many of the most common classes like UIImage, UIBezierPath and so on, all provide convenience methods for drawing their content within drawRect: using Core Graphics directly.

Update: The original post had a few bugs in it, mostly due to writing this post exclusively from memory during the Christmas holidays. As a result of some very helpful comments on this post however, I’ve had to make some changes. I apologize for the bugs in the first version.

UIImage drawing

We can use UIImage objects to directly draw to a Core Graphics context. Using raw API calls makes for very verbose code, since you’d have to create a CGImage object, a color-space object, and so forth. Instead, drawing an image can be as easy as the following.

With this technique you have many more options, such as blending and alpha arguments. There are other methods that let you draw an image at a particular point, drawing a pattern, and so forth.

UIBezierPath drawing

Often times you might want to show rounded corners on some view, but you only want certain corners to be rounded. For example lets assume you want to show a container view with only the top two corners rounded. The most straight-forward strategy is to drop into Core Graphics to render. While it’s possible to compose a path using low-level APIs alone, it’s quite simple to do the following.

Responding to user input

Core Graphics tries to be as efficient as possible, and so it takes some shortcuts on your behalf. For the most part these are exactly what you want and would expect, and come as a pleasant surprise when you come from other platforms. But there are times that it can get in your way, so knowing in advance what those optimizations are and how to control them helps. And knowing is half the battle…

Knowing when your view “Needs Display”

Your drawRect: method will only be invoked when UIKit feels your content has become stale and needs to be redrawn. For the most part this typically means your drawRect: method will be called when the view is added to a superview. Since drawRect is called when the frame is drawn, calling it explicitly won’t work as you’d expect, so if some user interaction requires you to redraw your content, there’s a simple solution for that.

All UIView objects have a method called “setNeedsDisplay“. Invoking this will toggle a flag inside your object indicating that your view needs to be redrawn upon the next frame. This has several advantages, including the fact that it makes your code very quick; this is because no matter how many times you call it within the same runloop, your frame will still only be redrawn once on the next frame. Therefore you don’t have to worry about over-invoking it, ensuring that you can keep your content fresh from wherever your UI logic determine content needs to change.

In the code example above you’ll notice that we’re creating the UIImage object in initWithFrame:, and are destroying it in dealloc, so that we don’t have to allocate objects within the drawRect: method. It’s important to keep that method as quick as possible so you don’t negatively impact your screen’s framerate.

Responding to frame size changes

When your view changes its size, either when explicitly set or when a superview autoresizes its subviews, your content will be invalidated in some way. But rather than calling expensive drawRect: methods all the time when this happens, UIView objects have a contentMode property that lets you give UIKit a hint about what you want it to do in this situation.

This property is an typedef enum named UIViewContentMode and has a bunch of different options that you should check out. The default value is UIViewContentModeScaleToFill which means if your frame’s size changes, UIKit will simply take the last-rendered result your drawRect: method created and will scale it up or down to fill the available space.

The simplest way to get your content to redraw is to set your view’s contentMode to UIViewContentModeRedraw, but in many cases this may be overkill.

For example, lets assume your drawRect: method draws a border and some comments along the bottom of your frame, as the “Like” example does in the previous section. If your frame changes its height it would be wasteful to perform a full redrawing operation when most of the content hasn’t changed. So if you set the contentMode to UIViewContentModeBottomLeft UIKit will align the content in the bottom-left corner of the view, cropping the rest of the content as it does so.

Avoid drawing across pixel boundaries

When you position elements on the screen (not just with Core Graphics, but in UIKit in general) you need to make sure that your views exist at integer pixel coordinates. iOS’ drawing coordinate system works in points, not absolute pixels, which means it’s possible to tell UIKit or Core Graphics to draw content at a fractional-pixel. Since this is impossible, the device attempts to anti-alias your content so that it blurs between several pixels. This is almost never what you want, so when calculating positions (especially when dividing one value by another) it’s important to use the floorf or ceilf functions to round the values down or up to the nearest integer.

This is more obvious when drawing or stroking lines or paths on the screen. Lets take the following example where we draw a single line along the bottom of the view. For this example however we’ll use the low-level drawing routines to compose a path, instead of using a UIBezierPath object.

This code seems pretty straightforward; move the drawing “pen” position to the bottom-left corner, add a line to the bottom-right corner, and stroke the resulting path with the line width and line color previously defined.

The “gotcha” here is that when you create a point at (0, 0), that represents the logical upper-left corner of the screen. However the pixel in the top-left corner occupies the space defined by the frame (0, 0, 1, 1) (e.g. a 1-point square starting at (0, 0)). When you draw a line it is centred on whatever coordinates you give it. Therefore in the code example above, your code is asking Core Graphics to draw a line that has 0.5 in one pixel, and 0.5 in another pixel.

To get around this you should offset your drawing frame so that you draw 0.5 points offset. Here’s an updated version of that code which draws a solid unblurred line:

Drawing with colour the easy way

Core Graphics has evolved over time, with new and improved APIs. This means that for many drawing operations there might be several different function calls that will give you the same result. This is especially true when dealing with colour.

For example, if you want to set the stroke (aka “line”) colour in Core Graphics, there are four separate function calls that will achieve the same result. Your impulse might be to pick the first result that comes up, but that means more work for yourself. The standard CGContextSetStrokeColor function call takes an array of CGFloats indicating the different colour values you want to draw with.

However there’s a much easier way. CGContextSetStrokeColorWithColor is a slightly more verbose function name, but allows you to supply a CGColorRef value to describe a colour. And as a convenience, the UIColor class provides a helper property called CGColor that returns a pre-calculated colour reference, which automatically releases its memory when the UIColor object goes away. This means you don’t need to manually malloc or release your colour references, and results in much less code.

You can see some of the code examples up above where I’ve already used this function and property. Whenever you see a function that has a “ColorWithColor” option, it uses this pattern.

Use the right tool for the job

Perhaps the best advice I can give is to use the right tool for the job.  For example, if you need to visually style some interface element, you shouldn’t add unnecessary subviews just to draw things like borders, rounded corners, tiled patterns, and so on.  Additionally there are cases where having a large number of subviews (such as in UITableViewCells) is inefficient and rendering simple elements like icons, text, or borders is better suited to Core Graphics.

iOS and UIKit is really a different environment than HTML, and you can’t treat device layout and rendering the same way.

And finally, make sure you practice, and don’t be afraid of trying out Core Graphics.  Start simple with things like borders, rendering images and text, or programmatically rendering effects that would otherwise be slow using Core Animation or image-based textures.

Tags:

About Michael Nachbaur

iOS app developer, livin' the dream. Working from wherever I find myself; Hawaii, Santa Monica, Vancouver, and elsewhere.

13 Responses to “Core Graphics isn’t scary, honest!”

  1. Julien Vignali January 2, 2012 5:08 am
    #

    Great article with some good tips!
    I am playing with UILabel drawing and was wondering if drawing about 20 labels in a UIView is faster and more memory-friendly than managing 20 UILabels objects (note: the text of the labels are static, they won’t change).
    What’s your opinion ?
    And what about using NSString -drawInRect: method instead of UILabel’s ? Does it make any difference ?
    Thanks :)

    • Michael Nachbaur January 2, 2012 10:02 pm
      #

      If the labels are static, you might as well use Core Graphics to render the labels (either using a UILabel or direct rendering with NSString, whichever makes the most sense for you). As long as you don’t need to re-layout your labels, and if you’re able to take advantage of the contentMode property enough, you’ll most likely never end up re-drawing that label much. So it’ll be blazing fast.

  2. Scott January 2, 2012 10:10 am
    #

    Your pixel drawing example could use much more detail. You mention floorf and then never use it. You subtract .5 from one of the Y positions, but not the other. Why? Why don’t you subtract it from the X coordinates too?

    • Michael Nachbaur January 2, 2012 10:09 pm
      #

      Yeah, I didn’t want this post to be too long, so I intentionally avoided diving into too much detail there.

      To answer your questions:

      1) I didn’t use floorf because I wanted to round the values up (using ceilf). There are cases where rounding down is preferred. For example, in an application I’m working on right now I need to render borders between different UIView subclasses, and the left/right borders need to line up. I have to use ceilf on one side and floorf on the other, so that number values are rounded up or down so the borders don’t overlap.

      2) The reason I subtracted 0.5 from the Y coordinate, but not the X coordinate, was because the line I was showing here was a horizontal line that extended along the entire width of the view. In a follow-up post I’ll provide some visuals to help explain this point more.

  3. Paul January 4, 2012 7:18 am
    #

    I think it’s great that you are putting effort in introducing newcomers to CoreGraphics but in this post beginners will pick up some things they probably should not be picking up.

    The docs for -[UILabel drawTextInRect:] state the following:

    “You should not call this method directly. This method should only be overridden by subclasses that want to modify the default drawing behavior for the label’s text.”

    Doing what you do in your code samples is ill-advised in terms of future-proofing your application.

    Apart from that there is no -[UIViewController drawRect:], so the code sample will not work and will probably add to the confusion among beginners about the role of UIViewController and UIView.

    • Michael Nachbaur January 5, 2012 11:17 am
      #

      Thank you for catching those bugs in my post. I wrote this from memory without having access to Xcode or the Xcode documentation over the Christmas holidays, so I obviously missed a few steps.

      I’ll update the post ASAP to fix these bugs. Again, thanks!

  4. Jean January 5, 2012 4:37 am
    #

    Hi
    I have tried to run the code uilabel drawing with the second drawRect. However, drawRect is not called, even when I use [self setNeedsDisplay].

    • Michael Nachbaur January 5, 2012 11:17 am
      #

      Hi Jean, as Paul commented up above, my post has a few bugs in its code samples, so I need to fix that.

  5. Vladimir Grichina January 10, 2012 3:24 pm
    #

    Note that you can also use [UIColor set], [UIColor setFill] and [UIColor setStroke] to set drawing color in a simpler way.

  6. Ultimate Outsourcing Destination February 27, 2012 10:09 pm
    #

    Well written great post. I wonder if there is a way to put shadow in a circular shape. Say in the bottom of an image, in the middle part, the shadow is in its longest size, but as it goes to the edges, it becomes shorter and shorter. On the corners, there are no shadows? Any idea?

    • Michael Nachbaur March 7, 2012 7:01 pm
      #

      That’s actually possible to do with plain Core Animation layers. Check out my post on Core Animation CALayer shadow paths. You can specify a custom CGPath for your shadow to follow, allowing you to extend it outside to a larger area.

  7. Vineesh TP February 29, 2012 11:09 pm
    #

    How can I use Core Graphics based on User Input.
    ie, I wanna to draw shapes by the coordinate values get from user as input.

    vineeshtp88@gmail.com

    • Michael Nachbaur March 8, 2012 6:21 am
      #

      In that case you need to have your drawing routines be aware of values you track (either have properties or ivars in your custom UIView that knows how to draw something based on those values), and whenever any of those values changes, you just need to call [myView setNeedsDisplay] to force it to be redrawn on the next frame.