iOS Recipes - Matt Drance [82]
The OBJC_ASSOCIATION_RETAIN_NONATOMIC parameter passed to objc_setAssociatedObject defines the storage policy—in this case, we want to retain the NSValue. We can also set a policy of assign or copy, just as with traditional Objective-C properties. The nameKey argument is used internally to store the value and must be unique. This ensures that no other associative references conflict with ours. The required type is void *, so the simplest solution is to declare a static char variable and pass its address, which is guaranteed to be unique.
TouchOrigin/UITouch+PRPAdditions.m
static char nameKey;
Fetching the origin point is simple: we pass the address of our nameKey to objc_getAssociatedObject and extract the CGPoint struct from the returned NSValue, reversing the coordinate conversion we performed in prp_saveOrigin. We first check to see whether the passed view has a window; otherwise, we could end up with a garbage return value.
TouchOrigin/UITouch+PRPAdditions.m
- (CGPoint)prp_originInView:(UIView *)view {
NSAssert((view.window != nil),
@"-prp_originInView: 'view' parameter is not in a window");
NSValue *valueObject = objc_getAssociatedObject(self, &nameKey);
CGPoint screenPoint = [valueObject CGPointValue];
screenPoint = [view.window convertPoint:screenPoint fromWindow:nil];
return [view convertPoint:screenPoint fromView:nil];
}
We can now make every UITouch object in our app remember where it started. We no longer have to manage this in our own controller code, and more importantly, that context is preserved no matter where the touch object happens to be passed.
Our code does, however, still need to set the initial point. We’ll do this, of course, in ‑touchesBegan:withEvent:. The accompanying TouchOrigin project does this in its PRPTrackingViewController class.
TouchOrigin/PRPTrackingViewController.m
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
NSLog(@"Touch %p began at %@", touch,
NSStringFromCGPoint([touch locationInView:touch.view]));
[touch prp_saveOrigin];
}
}
Once we set the origin point, we can use it again at any time. Subsequent touch events continue to use the same UITouch object, so the origin we set earlier will still be there.
TouchOrigin/PRPTrackingViewController.m
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
NSLog(@"Touch %p moved from %@ to %@", touch,
NSStringFromCGPoint([touch prp_originInView:touch.view]),
NSStringFromCGPoint([touch locationInView:touch.view]));
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
NSLog(@"Touch %p ended at %@; started at %@", touch,
NSStringFromCGPoint([touch locationInView:touch.view]),
NSStringFromCGPoint([touch prp_originInView:touch.view]));
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *touch in touches) {
NSLog(@"Touch %p cancelled at %@; started at %@", touch,
NSStringFromCGPoint([touch locationInView:touch.view]),
NSStringFromCGPoint([touch prp_originInView:touch.view]));
}
}
Run the TouchOrigin project from Xcode and watch the console to see a comparison of the touch’s origin point with its current location on each event. This example uses a view controller to listen for touch events, but you could follow this same pattern in a custom gesture recognizer or view as well.
We’ve done two very cool things in this recipe: added valuable context to UITouch objects and explored a powerful new language feature that makes Objective-C categories more useful than ever.
Footnotes
[8] Thanks to Colin Barrett for some late-night help deciding on a good example for this recipe.
Copyright © 2011, The Pragmatic Bookshelf.
You May Be Interested In…
Click a cover for more information
* * *
Table of Contents
iOS Recipes
Table of Contents
What Readers Are Saying About iOS Recipes
Foreword
Introduction
2.1 Formatting and Syntax
2.2 Categories