Channels ▼
RSS

Mobile

Gestures and Touches in iOS6: More Recipes


Multi-Touch touches are not grouped. If you touch the screen with two fingers from each hand, for example, there's no way to determine which touches belong to which hand. The touch order is also arbitrary. Although grouped touches retain the same finger order (or, more specifically, the same memory address) for the lifetime of a single touch event, from touch down through movement to release, the correspondence between touches and fingers may and likely will change the next time your user touches the screen. When you need to distinguish touches from each other, build a touch dictionary indexed by the touch objects, as shown in this recipe.

Perhaps it's a comfort to know that if you need it, the extra finger support has been built in. Unfortunately, when you are using three or more touches at a time, the screen has a pronounced tendency to lose track of one or more of those fingers. It's hard to programmatically track smooth gestures when you go beyond two finger touches. So instead of focusing on gesture interpretation, think of the Multi-Touch experience more as a series of time-limited independent interactions. You can treat each touch as a distinct item and process it independently of its fellows.

Recipe 3 adds Multi-Touch to a UIView by setting its multipleTouchEnabled property and tracing the lines that each finger draws. It does this by keeping track of each touch's physical address in memory but without pointing to or retaining the touch per Apple's recommendations.

This is, obviously, an oddball approach, but it has worked reliably throughout the history of the SDK. That's because each UITouch object persists at a single address throughout the touch-move-release lifecycle. Apple recommends against retaining UITouch instances, which is why the integer values of these objects are used as keys in this recipe. By using the physical address as a key, you can distinguish each touch, even as new touches are added or old touches are removed from the screen.

Be aware that new touches can start their lifecycle via touchesBegan:withEvent: independently of others as they move, end, or cancel. Your code should reflect that reality.

This recipe expands from Recipe 1. Each touch grows a separate Bezier path, which is painted in the view's drawRect method. Recipe 1 essentially started a new drawing at the end of each touch cycle. That worked well for application bookkeeping but failed when it came to creating a standard drawing application, where you expect to iteratively add elements to a picture.

Recipe 3 continues adding traces into a composite picture without erasing old items. Touches collect into an ever-growing mutable array, which can be cleared on user demand. This recipe draws in-progress tracing in a slightly lighter color, to distinguish it from paths that have already been stored to the drawing's stroke array.

Recipe 3: Accumulating User Tracings for a Composite Drawing
@interface TouchTrackerView : UIView
{
    NSMutableArray *strokes;
    NSMutableDictionary *touchPaths;
}
- (void) clear;
@end

@implementation TouchTrackerView

// Establish new views with storage initialized for drawing
- (id) initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame])
    {
        self.multipleTouchEnabled = YES;
        strokes = [NSMutableArray array];
        touchPaths = [NSMutableDictionary dictionary];
    }
    
    return self;
}

// On clear remove all existing strokes, but not in-progress drawing
- (void) clear
{
    [strokes removeAllObjects];
    [self setNeedsDisplay];
}

// Start touches by adding new paths to the touchPath dictionary
- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event
{
    for (UITouch *touch in touches)
    {
        NSString *key = [NSString stringWithFormat:@"%d", (int) touch];
        CGPoint pt = [touch locationInView:self];
        
        UIBezierPath *path = [UIBezierPath bezierPath];
        path.lineWidth = IS_IPAD? 8: 4;
        path.lineCapStyle = kCGLineCapRound;
        [path moveToPoint:pt];

        [touchPaths setObject:path forKey:key];
    }    
}
// Trace touch movement by growing and stroking the path
- (void) touchesMoved:(NSSet *) touches withEvent:(UIEvent *) event
{
    for (UITouch *touch in touches)
    {
        NSString *key = 
            [NSString stringWithFormat:@"%d", (int) touch];
        UIBezierPath *path = [touchPaths objectForKey:key];
        if (!path) break;
        
        CGPoint pt = [touch locationInView:self];
        [path addLineToPoint:pt];
    }    
    
    [self setNeedsDisplay];
}

// On ending a touch, move the path to the strokes array
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    for (UITouch *touch in touches)
    {
        NSString *key = [NSString stringWithFormat:@"%d", (int) touch];
        UIBezierPath *path = [touchPaths objectForKey:key];
        if (path) [strokes addObject:path];
        [touchPaths removeObjectForKey:key];
    }
    
    [self setNeedsDisplay];
}

- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self touchesEnded:touches withEvent:event];
}

// Draw existing strokes in dark purple, in-progress ones in light
- (void) drawRect:(CGRect)rect
{
    [COOKBOOK_PURPLE_COLOR set];
    for (UIBezierPath *path in strokes)
        [path stroke];
    
    [[COOKBOOK_PURPLE_COLOR colorWithAlphaComponent:0.5f] set];
    for (UIBezierPath *path in [touchPaths allValues])
        [path stroke];
}
@end

Note that Apple provides many Core Graphics/Quartz 2D resources on its developer website. Although many of these forums, mailing lists, and source code examples are not iOS specific, they offer an invaluable resource for expanding your iOS Core Graphics knowledge.

Detecting Circles

In a direct manipulation interface like iOS, you'd imagine that most people could get by just pointing to items onscreen. And yet, circle detection remains one of the most requested gestures. Developers like having people circle items onscreen with their fingers. In the spirit of providing solutions that readers have requested, Recipe 4 offers a relatively simple circle detector, which is shown in Figure 3.


Figure 3: The dot and the outer ellipse show the key features of the detected circle.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video