Here’s some sample code provided by Erica Sadun via Apple on the iphone-sdk list that was worked up to do the job of updating gesture-driven transformations on the fly. If you’ve struggled with doing this kind of thing so as to make the consequences work out intuitively — and if you haven’t, it’s not quite as straightforward as you think! — there’s probably some good tips for you to be found picking through this. Enjoy!
#import <UIKit/UIKit.h>
#include “time.h”
// Courtesy of Apple Sample Code
@interface UITouch (TouchSorting)
- (NSComparisonResult)compareAddress:(id)obj;
@end
@implementation UITouch (TouchSorting)
- (NSComparisonResult)compareAddress:(id)obj {
if ((void *)self < (void *)obj) return NSOrderedAscending;
else if ((void *)self == (void *)obj) return NSOrderedSame;
else return NSOrderedDescending;
}
@end
// Apple Sample Code again
@interface DragView : UIImageView {
CGAffineTransform originalTransform;
CFMutableDictionaryRef touchBeginPoints;
}
- (CGAffineTransform)incrementalTransformWithTouches:(NSSet *)touches;
- (void)updateOriginalTransformForTouches:(NSSet *)touches;
- (void)cacheBeginPointForTouches:(NSSet *)touches;
- (void)removeTouchesFromCache:(NSSet *)touches;
@end
@implementation DragView
- (CGAffineTransform)incrementalTransformWithTouches:(NSSet *)touches {
NSArray *sortedTouches = [[touches allObjects] sortedArrayUsingSelector:@selector(compareAddress:)];
NSInteger numTouches = [sortedTouches count];
// If there are no touches, simply return identify transform.
if (numTouches == 0) return CGAffineTransformIdentity;
// Single touch
if (numTouches == 1) {
UITouch *touch = [sortedTouches objectAtIndex:0];
CGPoint beginPoint = *(CGPoint*)CFDictionaryGetValue(touchBeginPoints, touch);
CGPoint currentPoint = [touch locationInView:self.superview];
return CGAffineTransformMakeTranslation(currentPoint.x – beginPoint.x, currentPoint.y – beginPoint.y);
}
// If two or more touches, go with the first two (sorted by address)
UITouch *touch1 = [sortedTouches objectAtIndex:0];
UITouch *touch2 = [sortedTouches objectAtIndex:1];
CGPoint beginPoint1 = *(CGPoint*)CFDictionaryGetValue(touchBeginPoints, touch1);
CGPoint currentPoint1 = [touch1 locationInView:self.superview];
CGPoint beginPoint2 = *(CGPoint*)CFDictionaryGetValue(touchBeginPoints, touch2);
CGPoint currentPoint2 = [touch2 locationInView:self.superview];
double layerX = self.center.x;
double layerY = self.center.y;
double x1 = beginPoint1.x – layerX;
double y1 = beginPoint1.y – layerY;
double x2 = beginPoint2.x – layerX;
double y2 = beginPoint2.y – layerY;
double x3 = currentPoint1.x – layerX;
double y3 = currentPoint1.y – layerY;
double x4 = currentPoint2.x – layerX;
double y4 = currentPoint2.y – layerY;
// Solve the system:
// [a b t1, -b a t2, 0 0 1] * [x1, y1, 1] = [x3, y3, 1]
// [a b t1, -b a t2, 0 0 1] * [x2, y2, 1] = [x4, y4, 1]
double D = (y1-y2)*(y1-y2) + (x1-x2)*(x1-x2);
if (D < 0.1) {
return CGAffineTransformMakeTranslation(x3-x1, y3-y1);
}
double a = (y1-y2)*(y3-y4) + (x1-x2)*(x3-x4);
double b = (y1-y2)*(x3-x4) – (x1-x2)*(y3-y4);
double tx = (y1*x2 – x1*y2)*(y4-y3) – (x1*x2 + y1*y2)*(x3+x4) + x3*(y2*y2 + x2*x2) + x4*(y1*y1 + x1*x1);
double ty = (x1*x2 + y1*y2)*(-y4-y3) + (y1*x2 - x1*y2)*(x3-x4) + y3*(y2*y2 + x2*x2) + y4*(y1*y1 + x1*x1);
return CGAffineTransformMake(a/D, -b/D, b/D, a/D, tx/D, ty/D);
}
- (void)updateOriginalTransformForTouches:(NSSet *)touches {
if ([touches count] > 0) {
CGAffineTransform incrementalTransform = [self incrementalTransformWithTouches:touches];
self.transform = CGAffineTransformConcat(originalTransform, incrementalTransform);
originalTransform = self.transform;
}
}
- (void)cacheBeginPointForTouches:(NSSet *)touches {
for (UITouch *touch in touches) {
CGPoint *point = (CGPoint*)CFDictionaryGetValue(touchBeginPoints, touch);
if (point == NULL) {
point = (CGPoint *)malloc(sizeof(CGPoint));
CFDictionarySetValue(touchBeginPoints, touch, point);
}
*point = [touch locationInView:self.superview];
}
}
- (void)removeTouchesFromCache:(NSSet *)touches {
for (UITouch *touch in touches) {
CGPoint *point = (CGPoint *)CFDictionaryGetValue(touchBeginPoints, touch);
if (point != NULL) {
free((void *)CFDictionaryGetValue(touchBeginPoints, touch));
CFDictionaryRemoveValue(touchBeginPoints, touch);
}
}
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
originalTransform = CGAffineTransformIdentity;
touchBeginPoints = CFDictionaryCreateMutable(NULL, 0, NULL, NULL);
self.userInteractionEnabled = YES;
self.multipleTouchEnabled = YES;
self.exclusiveTouch = NO;
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[[self superview] bringSubviewToFront:self]; // pop to front please!
NSMutableSet *currentTouches = [[[event touchesForView:self] mutableCopy] autorelease];
[currentTouches minusSet:touches];
if ([currentTouches count] > 0) {
[self updateOriginalTransformForTouches:currentTouches];
[self cacheBeginPointForTouches:currentTouches];
}
[self cacheBeginPointForTouches:touches];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
CGAffineTransform incrementalTransform = [self incrementalTransformWithTouches:[event touchesForView:self]];
self.transform = CGAffineTransformConcat(originalTransform, incrementalTransform);
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[self updateOriginalTransformForTouches:[event touchesForView:self]];
[self removeTouchesFromCache:touches];
for (UITouch *touch in touches) {
if (touch.tapCount >= 2) {
[self.superview bringSubviewToFront:self];
}
}
NSMutableSet *remainingTouches = [[[event touchesForView:self] mutableCopy] autorelease];
[remainingTouches minusSet:touches];
[self cacheBeginPointForTouches:remainingTouches];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[self touchesEnded:touches withEvent:event];
}
- (void)dealloc {
if (touchBeginPoints != NULL) CFRelease(touchBeginPoints);
[super dealloc];
}
@end
@interface HelloController : UIViewController
{
UIView *contentView;
}
@end
@implementation HelloController
CGPoint randomPoint() {return CGPointMake(random() % 256, random() % 396);}
- (void)loadView
{
// Create the main view
CGRect apprect = [[UIScreen mainScreen] applicationFrame];
contentView = [[UIView alloc] initWithFrame:apprect];
contentView.backgroundColor = [UIColor blackColor];
self.view = contentView;
[contentView release];
// Add the flowers to random points on the screen
for (int i = 0; i < 3; i++)
{
CGRect dragRect = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
dragRect.origin = randomPoint();
DragView *dragger = [[DragView alloc] initWithFrame:dragRect];
NSString *whichFlower = [[NSArray
arrayWithObjects:@"blueFlower.png", @"pinkFlower.png",
@"orangeFlower.png", nil] objectAtIndex:i];
[dragger setImage:[UIImage imageNamed:whichFlower]];
[dragger setUserInteractionEnabled:YES];
[contentView addSubview:dragger];
[dragger release];
}
}
-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return NO;
}
-(void) dealloc
{
[contentView release];
[super dealloc];
}
@end
@interface SampleAppDelegate : NSObject <UIApplicationDelegate> {
HelloController *hello;
}
@end
@implementation SampleAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application {
srandom(time(0));
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
hello = [[HelloController alloc] init];
[window addSubview:hello.view];
[window makeKeyAndVisible];
}
- (void)dealloc{
[hello release];
[super dealloc];
}
@end
int main(int argc, char *argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
int retVal = UIApplicationMain(argc, argv, nil, @”SampleAppDelegate”);
[pool release];
return retVal;
}
h/t: iphonesdk!
NOV