Read iOS Programming: The Big Nerd Ranch Guide, 3/e (Big Nerd Ranch Guides) Online
Authors: Aaron Hillegass,Joe Conway
Tags: #COM051370, #Big Nerd Ranch Guides, #iPhone / iPad Programming
The functions and types that begin with
CG
come from the Core Graphics framework, a C language API for 2D drawing. The hub of the Core Graphics framework is
CGContextRef
: all other Core Graphics functions and types interact with a drawing context in some way, and then the context creates an image.
Let’s look at the Core Graphics functions you used in the implementation of
HypnosisView
’s
drawRect:
method. You set up the drawing state with
CGContextSetLineWidth
and then set its color using
CGContextSetRGBStrokeColor
.
Next, you added a
path
to the context using
CGContextAddArc
. A path is a collection of points that forms a shape and can form anything from a square to a circle to a human outline. There are a number of Core Graphics functions like
CGContextAddArc
that you can use to add a path to a context. (Search the documentation for
CGContextRef
to find them.)
After a path has been added to a context, you can perform a drawing operation. There are three drawing operations:
After a drawing operation, the current path is removed from the context. Thus, to draw more than one circle, you have to add a path to the context for each circle. In
HypnosisView.m
, modify
drawRect:
to draw a number of concentric circles.
Build and run the application again. This time, each
HypnosisView
draws a number of circles.
That is one ugly screen; let’s get rid of one of the instances of
HypnosisView
and make the remaining one the size of the screen. In
HypnosisterAppDelegate.m
, modify
application:didFinishLaunchingWithOptions:
.
Build and run the application. Now you have a single instance of
HypnosisView
that fills the entire screen. Much better.
There are Core Graphics functions to draw most anything you like. Check the documentation for functions and types beginning with
CG
.
There are a number of Foundation and UIKit classes that can work with a
CGContextRef
. One such class is
UIColor
. An instance of
UIColor
represents a color and can be used to set the current drawing color of the context. In
HypnosisView.m
, replace the line of code that changes the stroke color with one that uses
UIColor
’s
colorWithRed:green:blue:alpha:
method.
Build and run the application again. The
HypnosisView
will look the same as before.
There are a number of prepared
UIColor
instances you can use for common colors. Change this line of code again:
Build and run again. The color of the circles should be about the same, but the code is much simpler.
NSString
has the ability to draw to a
CGContextRef
. Sending the message
drawInRect:withFont:
to an
NSString
will draw that string in the given rectangle with the given font to the current context. In
HypnosisView.m
, add the following code to the end of
drawRect:
.
Let’s spice it up with a shadow. Add the following code in
HypnosisView.m
.
Build and run the application. You now have shadow.
UIColor
’s
setStroke
method replaces
CGContextSetRGBStrokeColor
, but it is a one-to-one line replacement, so it doesn’t save that much work.
NSString
’s
drawInRect:withFont:
, on the other hand, buys you a lot of convenience.
UIImage
has a similar useful method,
drawInRect:
, for drawing an image object to a context.
When a
UIView
instance receives the message
setNeedsDisplay
, it will redraw its image. View subclasses send themselves
setNeedsDisplay
when their drawable content changes. For example, a
UILabel
will mark itself for re-display if it is sent the message
setText:
. (It has to redraw its image if the text it displays changes.)
The redrawing of a view’s image is not immediate; instead, it is added to a list of views that need updating. To understand when views actually get redrawn, we need to talk about the
run loop
– the infinite loop that comprises an iOS application. The run loop’s job is to listen for input (a touch, Core Location updates, data coming in through a network interface, etc.) and then find the appropriate handlers for that event (like an action or a delegate method for an object). Those handler methods call other methods, which call more methods, and so on. Once all the methods have completed, control returns to the run loop. At this point, the views are redrawn.
Figure 6.10
shows where redrawing views happens in the run loop.
Figure 6.10 Redrawing views with the run loop
When control returns to the run loop, it says,
“
Well, a bunch of code was just executed. I’m going to check if any views need to be redrawn.
”
Then the run loop prepares the necessary drawing contexts and sends the message
drawRect:
to all of the views that have been sent
setNeedsDisplay
in this iteration of the loop.
If one view redraws its image, the other views on the screen are not required to redraw theirs. Instead, their existing images will just be composited on the screen again. This optimization is why drawing and animation on iOS feels so responsive.
To see the effect of redrawing a view’s image, let’s give
HypnosisView
a
circleColor
property. Each circle that the
HypnosisView
draws will be the color pointed to by this property. In
HypnosisView.h
, declare this property.
In
HypnosisView.m
, synthesize this property to automatically create the instance variable, setter, and getter.
Now, in
HypnosisView.m
, update the
initWithFrame:
method to create a default
circleColor
.
Then, in
drawRect:
, modify the message that sets the context’s stroke color to use the
circleColor
instead of light gray.
Build and run the application. The circles are the same color because we don’t have a way to change
circleColor
from the default once the application is running. Let’s make the circle color change when the user shakes the device. To find out how to respond to a shake, we need to see how motion events are handled.