Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 88 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
88
Dung lượng
11,44 MB
Nội dung
ptg Recipe: Calculating Lines 323 CGContextAddLineToPoint(context, pt2.x, pt2.y); CGContextStrokePath(context); } } @end Get This Recipe’s Code To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 8 and open the project for this recipe. Recipe: Calculating Lines When user input relies primarily on touches, applied geometry can help interpret those gestures. In this recipe and the next, computational solutions filter user input to create simpler data sets that are more application appropriate. Recipe 8-10 collects the same touch array that was shown in Recipe 8-9.When the gesture finishes, that is, at touch-up, this code analyzes that array and creates a minimized set of line segments to match the freeform points. A reduced point set accomplishes two things. First, it creates a straighter, better-looking presentation.The right image in Figure 8-5 is much cleaner than the one on the left. Sec- ond, it produces a set of points that are better matched to interpretation.The six-point line segments shown in Figure 8-5 on the right are far easier to analyze than the more than 50 points on the left. The extra line segments are due to a slight finger squiggle at the top-right of the trian- gle. Converting a freeform gesture into meaningful user intent can be a significantly hard problem.Although it’s obvious to a human that the user meant to draw a triangle, compu- tational algorithms are never perfect.When you need to interpret gestures, a certain amount of hand waving accommodation is necessary. Recipe 8-10 works by analyzing sets of three points at a time. For each triplet, it cen- ters the first and third points around the origin of the second. It then takes the dot prod- uct of the vectors to the first and third points.The dot product returns a value that is the cosine of the angle between the two vectors. If those points are collinear, that is, the angle between them is roughly 180 degrees (give or take), the algorithm discards the middle point. The cosine of 180 degrees is -1.This code discards all points where the vector cosine falls below -0.75. Increasing the tolerance (by raising the cosine check, say to -0.6 or -0.5) produces flatter results but may also discard intentional direction changes from users. If your goal is to check for triangles, squares, and other simple polygons, the tolerance can be quite robust.To produce “prettier” line drawings, use a tighter tolerance to retain user-pro- vided detail. ptg Chapter 8 Gestures and Touches 324 Figure 8-5 Computational solutions can manage user input. Here, a line detection algorithm reduces the number of input points by converting user intent into a better geometric representation. Recipe 8-10 Creating Line Segments from Freeform Gestures // Return dot product of two vectors normalized float dotproduct (CGPoint v1, CGPoint v2) { float dot = (v1.x * v2.x) + (v1.y * v2.y); float a = ABS(sqrt(v1.x * v1.x + v1.y * v1.y)); float b = ABS(sqrt(v2.x * v2.x + v2.y * v2.y)); dot /= (a * b); return dot; } // remove all intermediate points that are approximately colinear - (void) touchesEnded:(NSSet *) touches withEvent:(UIEvent *) event { if (!self.points) return; if (self.points.count < 3) return; // Create the filtered array NSMutableArray *newpoints = [NSMutableArray array]; [newpoints addObject:[self.points objectAtIndex:0]]; CGPoint p1 = POINT(0); ptg Recipe: Detecting Circles 325 // Add only those points that are inflections for (int i = 1; i < (self.points.count - 1); i++) { CGPoint p2 = POINT(i); CGPoint p3 = POINT(i+1); // Cast vectors around p2 origin CGPoint v1 = CGPointMake(p1.x - p2.x, p1.y - p2.y); CGPoint v2 = CGPointMake(p3.x - p2.x, p3.y - p2.y); float dot = dotproduct(v1, v2); // Colinear items need to be as close as possible // to 180 degrees if (dot < -0.75f) continue; p1 = p2; [newpoints addObject:[self.points objectAtIndex:i]]; } // Add final point if ([newpoints lastObject] != [self.points lastObject]) [newpoints addObject:[self.points lastObject]]; // Report initial and final point counts NSLog(@"%@",[NSString stringWithFormat@"%d points to %d points", self.points.count, newpoints.count]); // Update with the filtered points and draw self.points = newpoints; [self setNeedsDisplay]; } Get This Recipe’s Code To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 8 and open the project for this recipe. Recipe: Detecting Circles In a direct manipulation interface like the iPhone, 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 8-11 offers a rela- tively simple circle detector, which is shown in Figure 8-6. In this implementation, detection uses a two-step test. First, there’s a convergence test. The circle must start and end close enough together that the points are somehow related. A fair amount of leeway is needed because when you don’t provide direct visual feedback, ptg Chapter 8 Gestures and Touches 326 Figure 8-6 The dot and the outer ellipse show the key features of the detected circle. users tend to undershoot or overshoot where they began.The pixel distance used here is a generous 60 pixels, approximately a third of the view size. The second test looks at movement around a central point. It adds up the arcs traveled, which should equal 360 degrees in a perfect circle.This sample allows any movement that falls within 45 degrees of that number. Upon passing the two tests, the algorithm produces a least bounding rectangle and centers that rectangle on the geometric mean of the points from the original gesture.This result is assigned to the circle instance variable. It’s not a perfect detection system (you can try to fool it when testing the sample code), but it’s robust enough to provide reasonably good circle checks for many iPhone applications. Recipe 8-11 Detecting Circles // At the end of touches, determine whether a circle was drawn - (void) touchesEnded:(NSSet *) touches withEvent:(UIEvent *) event { if (!self.points) return; if (self.points.count < 3) return; // Test 1: The start and end points must be between // 60 pixels of each other CGRect tcircle; if (distance(POINT(0), POINT(self.points.count - 1)) < 60.0f) ptg Recipe: Detecting Multitouch 327 tcircle = [self centeredRectangle]; // Test 2: Count the distance traveled in degrees. Must fall // within 45 degrees of 2 PI CGPoint center = CGPointMake(CGRectGetMidX(tcircle), CGRectGetMidY(tcircle)); float distance = ABS(acos(dotproduct(centerPoint(POINT(0), center), centerPoint(POINT(1), center)))); for (int i = 1; i < (self.points.count - 1); i++) distance += ABS(acos(dotproduct(centerPoint(POINT(i), center), centerPoint(POINT(i+1), center)))); if ((ABS(distance - 2 * M_PI) < (M_PI / 4.0f))) circle = tcircle; [self setNeedsDisplay]; } Get This Recipe’s Code To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 8 and open the project for this recipe. Recipe: Detecting Multitouch Enabling multitouch interaction in your UIViews lets the iPhone recover and respond to more than one finger touch at a time. Set the UIView property multipleTouchEnabled to YES or override isMultipleTouchEnabled for your view.When multitouch is enabled, each touch callback returns an entire set of touches.When that set’s count exceeds one, you know you’re dealing with multitouch. In theory, the iPhone could support an arbitrary number of touches. On the iPhone, multitouch is limited to five finger touches at a time. Even five at a time goes beyond what most developers need.There aren’t many meaningful gestures you can make with five fingers at once.This particularly holds true when you grasp the iPhone with one hand and touch with the other. Touches are not grouped. If, for example, you touch the screen with two fingers from each hand, there’s no way to determine which touches belong to which hand.The touch order is arbitrary. Although grouped touches retain the same finger order for the lifetime of a single touch event (down, move, up), the order may 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. Perhaps it’s a comfort to know that if you need to, 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 program- matically track smooth gestures when you go beyond two finger touches. Recipe 8-12 adds multitouch to a UIView (via the isMultipleToucheEnabled method) and draws lines between each touch location onscreen.When you limit your ptg Chapter 8 Gestures and Touches 328 input to two touches, it produces a reasonably steady response, maintaining a line between those two fingers.Add a third touch to the screen and the lines start to flicker.That’s because the iPhone does not steadily detect all the touches. Unfortunately, multitouch detection is not nearly as stable and dependable as single touch interaction.You see that in this recipe and see an even more pronounced example in Recipe 8-13.While multitouch is available and, admittedly, an exciting technology, its limits mean you should use it cautiously and with heavy testing before deployment to real-world applications. Recipe 8-12 Adding Basic Multitouch @implementation TouchView @synthesize points; - (BOOL) isMultipleTouchEnabled {return YES;} - (void) touchesBegan:(NSSet *) touches withEvent: (UIEvent *) event { self.points = [touches allObjects]; [self setNeedsDisplay]; } - (void) touchesMoved:(NSSet *) touches withEvent: (UIEvent *) event { self.points = [touches allObjects]; [self setNeedsDisplay]; } - (void) drawRect: (CGRect) rect { if (!self.points) return; if (self.points.count < 2) return; CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetLineWidth(context, 4.0f); [[UIColor redColor] set]; // Draw lines between each point CGPoint pt1 = POINT(0); CGContextMoveToPoint(context, pt1.x, pt1.y); for (int i = 1; i < self.points.count; i++) { pt1 = POINT(i % self.points.count); CGPoint pt2 = POINT((i + 1) % self.points.count); CGContextAddLineToPoint(context, pt2.x, pt2.y); } ptg Recipe: Gesture Distinction 329 CGContextStrokePath(context); } @end Get This Recipe’s Code To get the code used for this recipe, go to http://github.com/erica/iphone-3.0-cookbook-, or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 8 and open the project for this recipe. Note Apple provides many Core Graphics/Quartz 2D resources on its developer Web site. Although many of these forums, mailing lists, and source code samples are not iPhone spe- cific, they offer an invaluable resource for expanding your iPhone Core Graphics knowledge. Recipe: Gesture Distinction Standard Apple iPhone applications support a variety of gestures that have become a basic language for touch interaction. Users can tap, double-tap, swipe, and drag the screen, and Apple applications interpret those gestures accordingly. Unfortunately,Apple does not offer a public API that performs the heavy lifting.You need to interpret your own ges- tures. Recipe 8-13 offers a gesture detection system that waits for user input and then evaluates that input. Distinguishing gestures is not trivial, particularly when you add multitouch into the equation.As Recipe 8-12 demonstrated, iPhone touch sensors are less reliable in multi- touch mode.A two-touch drag, for example, might flip back and forth between detecting two fingers and one. The solution in Recipe 8-13 for working with this inconsistency is twofold. First, the code tries to find the most immediate solution for matching input to a known gesture as quickly as possible.When matched, it sets a “finished” flag so the first gesture matched wins. Second, this code may invalidate a match should user input continue beyond a rea- sonable limit. For example, taps are short; a tap should not involve 20 or 30 UITouch instances. Here are the gestures that Recipe 8-13 handles, and how it interprets them: n Swipes—Swipes are short, single-touch gestures that move in a single cardinal direction: up, down, left, or right.They cannot move too far off course from that primary direction.The code here checks for touches that travel at least 16 pixels in X or Y, without straying more than 8 pixels in another direction. n Pinches—To pinch or unpinch, a user must move two fingers together or apart in a single movement.That gesture must compress or expand by at least 8 pixels to regis- ter with this code. n Taps—Although a tap should ideally represent a single touch to the screen, extra callbacks may register. Recipe 8-13 uses a point limit of 3 for single-touch taps, and 10 for double-touch taps.And yes, that high tolerance is needed. Empirical testing ptg Chapter 8 Gestures and Touches 330 set the levels used in this recipe. Users touched one or two fingers to the screen at once, and the code counted the UITouch instances produced. n Double-taps—Each touch object provides a tap count, letting you check whether users tapped once or twice. However, a double-tap is not counted until a single-tap has already been processed.When looking to distinguish between single- and double-taps, be aware of this behavior. n Drags—For the purpose of this example, a drag refers to any single-touch event that is not a tap, a double-tap, or a swipe. Recipe 8-13 Interpreting Gestures @interface TouchView : UIView { BOOL multitouch; BOOL finished; CGPoint startPoint; NSUInteger touchtype; NSUInteger pointCount; UIViewController *vc; } @property (assign) UIViewController *vc; @end @implementation TouchView @synthesize vc; #define SWIPE_DRAG_MIN 16 #define DRAGLIMIT_MAX 8 #define POINT_TOLERANCE 16 #define MIN_PINCH 8 - (BOOL) isMultipleTouchEnabled {return YES;} - (void) touchesBegan:(NSSet *) touches withEvent: (UIEvent *) event { finished = NO; startPoint = [[touches anyObject] locationInView:self]; multitouch = (touches.count > 1); pointCount = 1; } - (void) touchesMoved:(NSSet *) touches withEvent: (UIEvent *) event { pointCount++; if (finished) return; ptg Recipe: Gesture Distinction 331 // Handle multitouch if (touches.count > 1) { // get touches UITouch *touch1 = [[touches allObjects] objectAtIndex:0]; UITouch *touch2 = [[touches allObjects] objectAtIndex:1]; // find current and previous points CGPoint cpoint1 = [touch1 locationInView:self]; CGPoint ppoint1 = [touch1 previousLocationInView:self]; CGPoint cpoint2 = [touch2 locationInView:self]; CGPoint ppoint2 = [touch2 previousLocationInView:self]; // calculate distances between the points CGFloat cdist = distance(cpoint1, cpoint2); CGFloat pdist = distance(ppoint1, ppoint2); multitouch = YES; // The pinch has to exceed a minimum distance to trigger if (ABS(cdist - pdist) < MIN_PINCH) return; if (cdist < pdist) touchtype = UITouchPinchIn; else touchtype = UITouchPinchOut; finished = YES; return; } else { // Check single touch for swipe CGPoint cpoint = [[touches anyObject] locationInView:self]; float dx = DX(cpoint, startPoint); float dy = DY(cpoint, startPoint); multitouch = NO; finished = YES; if ((dx > SWIPE_DRAG_MIN) && (ABS(dy) < DRAGLIMIT_MAX)) touchtype = UITouchSwipeLeft; else if ((-dx > SWIPE_DRAG_MIN) && (ABS(dy) < DRAGLIMIT_MAX)) touchtype = UITouchSwipeRight; else if ((dy > SWIPE_DRAG_MIN) && (ABS(dx) < DRAGLIMIT_MAX)) touchtype = UITouchSwipeUp; else if ((-dy > SWIPE_DRAG_MIN) && (ABS(dx) < DRAGLIMIT_MAX)) touchtype = UITouchSwipeDown; ptg Chapter 8 Gestures and Touches 332 else finished = NO; } } - (void) touchesEnded:(NSSet *) touches withEvent: (UIEvent *) event { // was not detected as a swipe if (!finished && !multitouch) { // tap or double tap if (pointCount < 3) { if ([[touches anyObject] tapCount] == 1) touchtype = UITouchTap; else touchtype = UITouchDoubleTap; } else touchtype = UITouchDrag; } // did points exceeded proper swipe? if (finished && !multitouch) { if (pointCount > POINT_TOLERANCE) touchtype = UITouchDrag; } // Is this properly a tap/double tap? if (multitouch || (touches.count > 1)) { // tolerance is *very* high if (pointCount < 10) { if ([[touches anyObject] tapCount] == 1) touchtype = UITouchMultitouchTap; else touchtype = UITouchMultitouchDoubleTap; } } NSString *whichItem = nil; if (touchtype == UITouchUnknown) whichItem = @"Unknown"; else if (touchtype == UITouchTap) whichItem = @"Tap"; else if (touchtype == UITouchDoubleTap) [...]... outlets and actions in the Library > Classes pane before you make your connections .The switch should trigger on Value Changed and send the doSwitch: action to the File’s Owner, that is, the main view controller .The controller then sets the enabled property for the button Unfortunately, you cannot connect the switch directly to the button inside IB to tie the switch value to the button’s enabled property... updateOriginalTransformForTouches:currentTouches]; [self cacheBeginPointForTouches:currentTouches]; } [self cacheBeginPointForTouches:touches]; } // During movement, update the transform to match the touches - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { CGAffineTransform incrementalTransform = [self incrementalTransformWithTouches:[event touchesForView:self]]; [self setConstrainedTransform:... [self.vc performSelector:@selector(updateStatewithPoints:) withObject:whichItem withObject:[NSNumber numberWithInt:pointCount]]; } @end Get This Recipe’s Code To get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter 8 and open the project for this recipe... disk image containing all of the sample code from the book, go to the folder for Chapter 9 and open the project for this recipe Multiline Button Text New to the 3.0 SDK, UIButtons now offer access to their title label via the titleLabel property By exposing this property, the SDK allows you to modify the title attributes directly, including its font and line break mode Here, the font is set to a very... CGAffineTransformConcat(self.transform, CGAffineTransformMakeScale(1.0f, (MINZOOM * originalSize.height / asize.height))); self.transform = concat; } } // Apply touches to create transform - (void)updateOriginalTransformForTouches:(NSSet *)touches { if ([touches count] > 0) { CGAffineTransform incrementalTransform = [self incrementalTransformWithTouches:touches]; [self setConstrainedTransform: CGAffineTransformConcat(originalTransform,... Initialize the danger button as semi-transparent dangerButton.alpha = 0.25f; [dangerButton addTarget:self action:@selector(boom) forControlEvents:UIControlEventTouchUpInside]; } @end Get This Recipe’s Code To get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for Chapter... fear .The Core Graphics calls are fast, and the memory requirements for the thumb-sized images are minimal This particular recipe assigns two thumb images to the slider .The bubble appears only when the slider is in use, for its UIControlStateHighlighted In its normal state, namely UIControlStateNormal, only the smaller rectangular thumb appears Users can tap on the thumb to review the current setting .The. .. mimics the letter highlights on the standard iPhone keyboard To accommodate these changes in art, the slider updates its frame at the start and end of each gesture On being touched (UIControlEventTouchDown), the frame expands by sixty pixels in height to the thumbFrame.This extra space provides enough room to show the expanded thumb during interaction 357 358 Chapter 9 Building and Using Controls When the. .. action:@selector(toggleButton) forControlEvents: UIControlEventTouchUpInside]; Adding Animated Elements to Buttons // For tracking the two states isOn = NO; [self toggleButton:button]; // Place the button into the view The button is autoreleased [self.view addSubview:button]; } Get This Recipe’s Code To get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk... precooked button type or build a custom button from scratch .The current iPhone SDK offers the following precooked types.As you can see, the buttons available are not general purpose.They were added to the SDK primarily for Apple’s convenience, not yours Nonetheless, you can use these in your programs as needed Figure 9-2 shows each button Figure 9-2 The iPhone SDK offers five precooked button types, which . get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for. at a time. For each triplet, it cen- ters the first and third points around the origin of the second. It then takes the dot prod- uct of the vectors to the first and third points .The dot product. get the code used for this recipe, go to http://github.com/erica /iphone- 3.0 -cookbook- , or if you’ve downloaded the disk image containing all of the sample code from the book, go to the folder for