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
As a subclass of
UIResponder
, your view can override four methods to handle the four distinct touch events:
- (void)
touchesBegan:
(NSSet *)touches
withEvent:
(UIEvent *)event;
- (void)
touchesMoved:
(NSSet *)touches
withEvent:
(UIEvent *)event;
- (void)
touchesEnded:
(NSSet *)touches
withEvent:
(UIEvent *)event;
- (void)
touchesCancelled:
(NSSet *)touches
withEvent:
(UIEvent *)event;
When a finger touches the screen, an instance of
UITouch
is created. The
UIView
that this finger touched is sent the message
touchesBegan:withEvent:
and the
UITouch
is in the
NSSet
of
touches
.
As that finger moves around the screen, the touch object is updated to contain the current location of the finger on the screen. Then, the same
UIView
that the touch began on is sent the message
touchesMoved:withEvent:
. The
NSSet
that is passed as an argument to this method contains the same
UITouch
that originally was created when the finger it represents touched the screen.
When a finger is removed from the screen, the touch object is updated one last time to contain the current location of the finger, and the view that the touch began on is sent the message
touchesEnded:withEvent:
. After that method finishes executing, the
UITouch
object is destroyed.
From this information, we can draw a few conclusions about how touch objects work:
Every time a touch does something, like begins, moves, or ends, a
touch event
is added to a queue of events that the
UIApplication
object manages. In practice, the queue rarely fills up, and events are delivered immediately. The delivery of these touch events involves sending one of the
UIResponder
messages to the view that owns the touch. (If your touches are sluggish, then one of your methods is hogging the CPU, and events are waiting in line to be delivered.
Chapter 21
will show you how to catch these problems.)
What about multiple touches? If multiple fingers do the same thing at the exact same time to the same view, all of these touch events are delivered at once. Each touch object – one for each finger – is included in the
NSSet
passed as an argument in the
UIResponder
messages. However, the window of opportunity for the
“
exact same time
”
is fairly short. So, instead of one responder message with all of the touches, there are usually multiple responder messages with one or more of the touches.
Now let’s get started with your application. In
Xcode
, create a new
Empty Application
iPhone project and name it
TouchTracker
. Don’t specify a
Class Prefix
and only check the box for
Automatic Reference Counting
.
First, you will need a model object that describes a line. Create a new
NSObject
and name it
Line
. In
Line.h
, declare two
CGPoint
properties:
In
Line.m
, synthesize the properties:
Next, create a new
NSObject
called
TouchDrawView
. In
TouchDrawView.h
, change the superclass to
UIView
. Also, declare two collection objects: an array to hold complete lines and a dictionary to hold lines that are still being drawn. You’ll see why we’re using different collection objects shortly.
Now you need a view controller to manage an instance of
TouchDrawView
in
TouchTracker
. Create a new
NSObject
subclass named
TouchViewController
. In
TouchViewController.h
, change the superclass to
UIViewController
.
In
TouchViewController.m
, override
loadView
to set up an instance of
TouchDrawView
as
TouchViewController
’s
view
. Make sure to import the header file for
TouchDrawView
at the top of this file.
In
AppDelegate.m
, create an instance of
TouchViewController
and set it as the
rootViewController
of the window. Don’t forget to import the header file for
TouchViewController
in this file.
TouchDrawView
will keep track of all of the lines that have been drawn and any that are currently being drawn. In
TouchDrawView.m
, create the two collections and import the header for the
Line
class.
Notice that you explicitly enabled multi-touch events by sending the message
setMultipleTouchEnabled:
. Without this, only one touch at a time can be active on a view. If another finger touches the view, it will be ignored, and the view will not be sent
touchesBegan:withEvent:
or any of the other
UIResponder
messages.
Now override the
drawRect:
method to create lines using functions from Core Graphics:
Finally, write a method that will clear the collections and redraw the view in
TouchDrawView.m
.
A line (remember 9th grade geometry class?) is defined by two points. Our
Line
stores these points as properties named
begin
and
end
. When a touch begins, you’ll create a line and set both
begin
and
end
to the point where the touch began. When the touch moves, you will update
end
. When the touch ends, you will have your complete line.
There are two collection objects that hold
Line
instances. Lines that have been completed are stored in the
completeLines
array. Lines that are still being drawn, however, are stored in an
NSMutableDictionary
. Why do we need a dictionary? We’ve enabled multi-touch, so a user can draw more than one line at a time. This means we have to keep track of which touch events go with which line. For instance, imagine the user touches the screen with two fingers creating two instances of
Line
. Then one of those fingers moves. The
TouchDrawView
is sent a message for the event, but how can it know which line to update?
This is why we’re using a dictionary instead of an array for the lines in process. When a touch begins, we will grab the address of the
UITouch
object that is passed in and wrap it in an
NSValue
instance. A new
Line
will be created and added to the dictionary, and the
NSValue
will be its key. As we receive more touch events, we can use the address of the
UITouch
that is passed in to access and update the right line (
Figure 19.2
).
Figure 19.2 Object diagram for TouchTracker
Now let’s return to the methods for handling touch events. First, in
TouchDrawView.m
, override
touchesBegan:withEvent:
to create a new
Line
instance and store it in an
NSMutableDictionary
.
Next, in
TouchDrawView.m
, override
touchesMoved:withEvent:
to update the end point of the line associated with the moving touch.
When a touch ends, you need to finalize the line. However, a touch can end for two reasons: the user lifts the finger off the screen (
touchesEnded:withEvent:
) or the operating system interrupts your application (
touchesCancelled:withEvent:
). A phone call, for example, will interrupt your application.
In many applications, you’ll want to handle these two events differently. However, for
TouchTracker
, you will write one method to handle both cases. Declare a new method in
TouchDrawView.h
.
In
TouchDrawView.m
, implement
endTouches:
.
Finally, override the two methods from
UIResponder
to call
endTouches:
in
TouchDrawView.m
.
Build and run the application. Then make beautiful line art with one or more fingers.