Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Beginning iOS5 Development.pdf
Скачиваний:
7
Добавлен:
09.05.2015
Размер:
15.6 Mб
Скачать

CHAPTER 17: Taps, Touches, and Gestures

627

Figure 17–5. The PinchMe application detects the pinch guesture, both for zooming in and zooming out.

Defining Custom Gestures

You’ve now seen how to detect the most commonly used iPhone gestures. The real fun begins when you start defining your own custom gestures! You’ve already learned how to use a few of UIGestureRecognizer’s subclasses, so now it’s time to learn how to create your own gestures, which can be easily attached to any view you like.

Defining a custom gesture is tricky. You’ve already mastered the basic mechanism, and that wasn’t too difficult. The tricky part is being flexible when defining what constitutes a gesture.

Most people are not precise when they use gestures. Remember the variance we used when we implemented the swipe, so that even a swipe that wasn’t perfectly horizontal or vertical still counted? That’s a perfect example of the subtlety you need to add to your own gesture definitions. If you define your gesture too strictly, it will be useless. If you define it too generically, you’ll get too many false positives, which will frustrate the user. In a sense, defining a custom gesture can be hard because you must be precise about a gesture’s imprecision. If you try to capture a complex gesture like, say, a figure eight, the math behind detecting the gesture is also going to get quite complex.

www.it-ebooks.info

628

CHAPTER 17: Taps, Touches, and Gestures

The CheckPlease Application

In our sample, we’re going to define a gesture shaped like a check mark (see Figure 17–6).

Figure 17–6. An illustration of our check-mark gesture

What are the defining properties of this check-mark gesture? Well, the principal one is that sharp change in angle between the two lines. We also want to make sure that the user’s finger has traveled a little distance in a straight line before it makes that sharp angle. In Figure 17–6, the legs of the check mark meet at an acute angle, just under 90 degrees. A gesture that required exactly an 85-degree angle would be awfully hard to get right, so we’ll define a range of acceptable angles.

Create a new project in Xcode using the Single View Application template, and call the project CheckPlease. In this project, we’re going to need to do some fairly standard analytic geometry to calculate such things as the distance between two points and the angle between two lines. Don’t worry if you don’t remember much geometry; we’ve provided you with functions that will do the calculations for you.

Look in the 17 - CheckPlease folder for two files, named CGPointUtils.h and CGPointUtils.c. Drag both of these to the CheckPlease folder of your project. Feel free to use these utility functions in your own applications.

www.it-ebooks.info

CHAPTER 17: Taps, Touches, and Gestures

629

Control-click in the CheckPlease folder, and add a new file to the project. Use the file-creation assistant to create a new Objective-C class called

BIDCheckMarkRecognizer. In the Subclass of control, type UIGestureRecognizer. Then select BIDCheckMarkRecognizer.h, and make the following changes:

#import <UIKit/UIKit.h>

@interface BIDCheckMarkRecognizer : UIGestureRecognizer

@property (assign, nonatomic) CGPoint lastPreviousPoint; @property (assign, nonatomic) CGPoint lastCurrentPoint; @property (assign, nonatomic) CGFloat lineLengthSoFar;

@end

Here, we declare three properties: lastPreviousPoint, lastCurrentPoint, and lineLengthSoFar. Each time we’re notified of a touch, we’re given the previous touch point and the current touch point. Those two points define a line segment. The next touch adds another segment. We store the previous touch’s previous and current points in lastPreviousPoint and lastCurrentPoint, which gives us the previous line segment. We can then compare that line segment to the current touch’s line segment. Comparing these two line segments can tell us if we’re still drawing a single line or if there’s a sharp enough angle between the two segments that we’re actually drawing a check mark.

Remember that every UITouch object knows its current position in the view, as well as its previous position in the view. In order to compare angles, however, we need to know the line that the previous two points made, so we need to store the current and previous points from the last time the user touched the screen. We’ll use these two variables to store those two values each time this method is called, so that we have the ability to compare the current line to the previous line and check the angle.

We also declare a property to keep a running count of how far the user has dragged the finger. If the finger hasn’t traveled at least 10 pixels (a value we’ll soon define in kMinimumCheckMarkLength), whether the angle falls in the correct range doesn’t matter. If we didn’t require this distance, we would receive a lot of false positives.

Now select BIDCheckMarkRecognizer.m, and make the following changes:

#import "BIDCheckMarkRecognizer.h"

#import "CGPointUtils.h"

#import <UIKit/UIGestureRecognizerSubclass.h>

#define kMinimumCheckMarkAngle

50

#define

kMaximumCheckMarkAngle

135

#define

kMinimumCheckMarkLength

10

@implementation BIDCheckMarkRecognizer

@synthesize lastPreviousPoint; @synthesize lastCurrentPoint; @synthesize lineLengthSoFar;

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event];

UITouch *touch = [touches anyObject];

www.it-ebooks.info

630

CHAPTER 17: Taps, Touches, and Gestures

CGPoint point = [touch locationInView:self.view]; lastPreviousPoint = point;

lastCurrentPoint = point; lineLengthSoFar = 0.0f;

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesMoved:touches withEvent:event];

UITouch *touch = [touches anyObject];

CGPoint previousPoint = [touch previousLocationInView:self.view]; CGPoint currentPoint = [touch locationInView:self.view];

CGFloat angle = angleBetweenLines(lastPreviousPoint, lastCurrentPoint, previousPoint, currentPoint);

if (angle >= kMinimumCheckMarkAngle && angle <= kMaximumCheckMarkAngle && lineLengthSoFar > kMinimumCheckMarkLength) {

self.state = UIGestureRecognizerStateEnded;

}

lineLengthSoFar += distanceBetweenPoints(previousPoint, currentPoint); lastPreviousPoint = previousPoint;

lastCurrentPoint = currentPoint;

}

@end

After importing CGPointUtils.h, the file we mentioned earlier, we import a special header file called UIGestureRecognizerSubclass.h, which contains declarations that are intended for use only by a subclass. The important thing this does is to make the gesture recognizer’s state property writable. That’s the mechanism our subclass will use to affirm that the gesture we’re watching was successfully completed.

Then we define the parameters that we use to decide whether the user’s fingersquiggling matches our definition of a check mark. You can see that we’ve defined a minimum angle of 50 degrees and a maximum angle of 135 degrees. This is a pretty broad range, and depending on your needs, you might decide to restrict the angle. We experimented a bit with this and found that our practice check-mark gestures fell into a fairly broad range, which is why we chose a relatively large tolerance here. We were somewhat sloppy with our check-mark gestures, and so we expect that at least some of our users will be as well. As a wise man once said, “Be rigorous in what you produce and tolerant in what you accept.”

The CheckPlease Touch Methods

Let’s take a look at the touch methods. You’ll notice that each of them first calls the superclass’s implementation—something we’ve never done before. We need to do this in a UIGestureRecognizer subclass so that our superclass can have the same amount of knowledge about the events as we do. Now, on to the code itself.

In touchesBegan:withEvent:, we determine the point that the user is currently touching and store that value in lastPreviousPoint and lastCurrentPoint. Since this method is

www.it-ebooks.info

CHAPTER 17: Taps, Touches, and Gestures

631

called when a gesture begins, we know there is no previous point to worry about, so we store the current point in both. We also reset the running line length count to 0.

Then, in touchesMoved:withEvent:, we calculate the angle between the line from the current touch’s previous position to its current position, and the line between the two points stored in the lastPreviousPoint and lastCurrentPoint instance variables. Once we have that angle, we check to see if it falls within our range of acceptable angles and check to make sure that the user’s finger has traveled far enough before making that sharp turn. If both of those are true, we set the label to show that we’ve identified a check-mark gesture. Next, we calculate the distance between the touch’s position and its previous position, add that to lineLengthSoFar, and replace the values in lastPreviousPoint and lastCurrentPoint with the two points from the current touch so we’ll have them next time through this method.

Now that we have a gesture recognizer of our own to try out, it’s time to connect it to a view, just as we’ve done with the others we’ve used. Select BIDViewController.h, and make the following change:

#import <UIKit/UIKit.h>

@interface BIDViewController : UIViewController

@property (weak, nonatomic) IBOutlet UILabel *label;

@end

Here, we simply define an outlet to a label that we’ll use to inform the user when we’ve detected a check-mark gesture.

Select BIDViewController.xib to edit the GUI. Add a Label from the library to the upper-left blue guideline, resize it so it spans from left blue guideline to right blue guideline, and set its alignment to centered. Control-drag from the File’s Owner icon to that label to connect it to the label outlet, and double-click the label to delete its text. Save the nib file.

Now, switch over to BIDViewController.m, and add the following code to the top of the file:

#import "BIDViewController.h"

#import "BIDCheckMarkRecognizer.h"

@implementation BIDViewController

@synthesize label;

- (void)doCheck:(BIDCheckMarkRecognizer *)check { label.text = @"Checkmark";

[self performSelector:@selector(eraseLabel) withObject:nil afterDelay:1.6];

}

- (void)eraseLabel { label.text = @"";

}

.

.

.

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]