Gold Challenge


#1

Just to keep my run going on posts in this chapter, here’s my solution to the Gold Challenge. Basically, I added a Circle Class with two properties, an origin and a radius. To the TouchDrawView, I added a couple of variables to hold the radius and origin, though I know that I probably don’t need them. I also added a couple of methods to compute the origin and radius from the touch locations. Here’s my code.

Circle.h (Circle.m does not contain anything)

@interface Circle : NSObject

@property (nonatomic) CGPoint origin;
@property (nonatomic) CGFloat radius;

@end

TouchDrawView.h

@interface TouchDrawView : UIView
{
    NSMutableDictionary *linesInProcess;
    NSMutableArray *completeLines;
    
    NSMutableDictionary *circlesInProcess;
    NSMutableArray *completeCircles;
    CGFloat radius;
    CGPoint origin;
}

-(void)clearAll;
-(void)endTouches:(NSSet *)touches;
-(CGFloat)radiusFromTouches:(NSSet *)touches;
-(CGPoint)circleOriginFromTouches:(NSSet *)touches;

@end

TouchDrawView.m

#import "TouchDrawView.h"
#import "Line.h"
#import "Circle.h"

@implementation TouchDrawView

-(id)initWithFrame:(CGRect)r
{
    self = [super initWithFrame:r];
    
    if (self)
    {
        linesInProcess = [[NSMutableDictionary alloc] init];
        completeLines  = [[NSMutableArray alloc] init];
        
        circlesInProcess = [[NSMutableDictionary alloc] init];
        completeCircles  = [[NSMutableArray alloc] init];

        
        [self setBackgroundColor:[UIColor whiteColor]];
        [self setMultipleTouchEnabled:YES];
    }
    
    return self;
}

-(void)touchesBegan:(NSSet *)touches
          withEvent:(UIEvent *)event
{
    for (UITouch *t in touches)
    {
        if ([t tapCount] > 1)
        {
            [self clearAll];
            return;
        }
        
        if ([touches count] == 2) //If there are exactly two touches, draw a circle
        {
            origin = [self circleOriginFromTouches:touches];
            radius = [self radiusFromTouches:touches];
            Circle *newCircle = [[Circle alloc] init];
            [newCircle setOrigin:origin];
            [newCircle setRadius:radius];
            
            NSValue *key = [NSValue valueWithNonretainedObject:t];
            [circlesInProcess setObject:newCircle forKey:key];
            
            return;
        }
        
        else
        {
            //Use touch object packed in a nsvalue as key
            NSValue *key = [NSValue valueWithNonretainedObject:t];
            
            //Create a line for the value
            CGPoint loc = [t locationInView:self];
            Line *newLine = [[Line alloc] init];
            [newLine setBegin:loc];
            [newLine setEnd:loc];
            
            [linesInProcess setObject:newLine forKey:key];
        }
    }
}

-(void)touchesMoved:(NSSet *)touches
          withEvent:(UIEvent *)event
{
    for (UITouch *t in touches)
    {
        if ([touches count] == 2)
        {
            NSValue *key = [NSValue valueWithNonretainedObject:t];
            Circle *circle = [circlesInProcess objectForKey:key];
            
            [circle setOrigin:[self circleOriginFromTouches:touches]];
            [circle setRadius:[self radiusFromTouches:touches]];
            
            [self setNeedsDisplay];
            return;
        }
        
        else
        {
            //Get the key then value fron the touch event
            NSValue *key = [NSValue valueWithNonretainedObject:t];
            Line *line = [linesInProcess objectForKey:key];
            
            //Update the end point
            CGPoint loc = [t locationInView:self];
            [line setEnd:loc];
        }
    }
    
    [self setNeedsDisplay];
}

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

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

-(void)endTouches:(NSSet *)touches
{
    for (UITouch *t in touches)
    {
        if ([touches count] == 2)
        {
            NSValue *key = [NSValue valueWithNonretainedObject:t];
            Circle *circle = [circlesInProcess objectForKey:key];
            
            if (circle)
            {
                [completeCircles addObject:circle];
                [circlesInProcess removeAllObjects];
            }
            
            [self setNeedsDisplay];
            return;
            
        }
        
        else
        {
            NSValue *key = [NSValue valueWithNonretainedObject:t];
            Line *line = [linesInProcess objectForKey:key];
            
            if (line)
            {
                [completeLines addObject:line];
                [linesInProcess removeObjectForKey:key];
            }
        }
    }
    
    [self setNeedsDisplay];
}

-(CGPoint)circleOriginFromTouches:(NSSet *)touches
{
    NSArray *pointArray = [[NSArray alloc] initWithArray:[touches allObjects]];
    CGPoint p1 = [[pointArray objectAtIndex:0] locationInView:self];
    CGPoint p2 = [[pointArray objectAtIndex:1] locationInView:self];
    
    origin = CGPointMake((p1.x + p2.x) * 0.5, (p1.y + p2.y) * 0.5);
        
    return origin;
}

-(CGFloat )radiusFromTouches:(NSSet *)touches
{
    //the two touches represent the diameter of the circle.
    //1/2 this distance is the radius
    NSArray *pointArray = [[NSArray alloc] initWithArray:[touches allObjects]];
    CGPoint p1 = [[pointArray objectAtIndex:0] locationInView:self];
    CGPoint p2 = [[pointArray objectAtIndex:1] locationInView:self];
    
    radius = sqrt(powf(p1.x - p2.x, 2) + powf(p1.y - p2.y, 2)) * 0.5;

    return radius;
}

-(void)drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 10.0);
    CGContextSetLineCap(context, kCGLineCapRound);
    
    //Draw Complete Lines and Circles in Black
    [[UIColor blackColor] set];
    
    for (Circle *circle in completeCircles)
    {
        radius = [circle radius];
        origin = [circle origin];
        CGContextAddArc(context, origin.x, origin.y, radius, 0, 2*M_PI, 1);
        CGContextStrokePath(context);
    }
    
    
    for (Line *line in completeLines)
    {
        CGContextMoveToPoint(context, [line begin].x, [line begin].y);
        CGContextAddLineToPoint(context, [line end].x, [line end].y);
        CGContextStrokePath(context);
    }
    
    
    //Draw Lines in Process in Red
    [[UIColor redColor] set];
    
    for (NSValue *v in circlesInProcess)
    {
        Circle *circle = [circlesInProcess objectForKey:v];
        origin = [circle origin];
        radius = [circle radius];
        CGContextAddArc(context, origin.x, origin.y, radius, 0, 2*M_PI, 1);
        CGContextStrokePath(context);
    }
    
    for (NSValue *v in linesInProcess)
    {
        Line *line = [linesInProcess objectForKey:v];
        CGContextMoveToPoint(context, [line begin].x, [line begin].y);
        CGContextAddLineToPoint(context, [line end].x, [line end].y);
        CGContextStrokePath(context);
    }
}

-(void)clearAll
{
    [linesInProcess removeAllObjects];
    [completeLines removeAllObjects];
    [circlesInProcess removeAllObjects];
    [completeCircles removeAllObjects];
    
    [self setNeedsDisplay];
}

@end

#2

Very interesting solution. Clean an concise, unlike mine. A few comments after studying your code:

  1. The challenge mentions that each finger should be a corner of the rect around the circle, and so I understand that we should be able to draw all kinds of ellipse, and not only perfect circles. That changes the solution considerably.

  2. Your use of “NSArray *pointArray = [[NSArray alloc] initWithArray:[touches allObjects]];” was brilliant. I’ve never seen it before in the docs I read.

  3. I noticed that you kept using a UITouch wrapped in a NSValue as the key for the circlesInProgress dictionary. In other words, you are storing the (in process) circle using just one of the two finger as it’s key, and I believe that imposes a problem. If the user moves the other finger, the model would not find the circle inside the dictionary. Or, as I believe its happening here, it uses the “free” unbound second finger to start other lines WHILE the circle is still being constructed. In my attempt to fix this in my code I’m using the UIEvent wrapped in NSValue as the key in the dictionary, and it works, although I’m not sure yet if it is the right approach.

  4. Checking your code for bugs made me find a bajillion bugs on mine. Thank you for that =)

Cheers!


#3

Another observation related to this exercise:

Testing my solution on the iPad simulator gave me no crashes. But testing the same code on the actual device is giving me multiple crashes. My finds so far show me one interesting thing: removing only one finger from the screen while drawing a circle causes the crash, something that it’s not possible to do on the simulator.

That’s, of course, a problem in my code that I have to fix, but I thought this could be useful to other as well.


#4

This is my current solution. Works like a charm on the iPad simulator. On the actual device it crashes when taking one finger off the screen while creating an ellipse, as I mentioned before. I’ll fix that soon, and post an update. Comments and questions are really welcomed.

And yes, attending an Art college really gave me an upper-hand in this challenge. :laughing:

[code]#import “TouchDrawView.h”
#import “RetanguloStore.h”
#import “Retangulo.h”
#import “LineStore.h”
#import “Line.h”

@implementation TouchDrawView
@synthesize completeLines, completeRects;

  • (id)initWithFrame:(CGRect)frame lines:(NSMutableArray *)lines
    {
    self = [self initWithFrame:frame];

    if (self) {
    completeLines = lines;
    }

    return self;
    }

  • (id)initWithFrame:(CGRect)frame
    {
    self = [super initWithFrame:frame];

    if (self) {
    linesInProgress = [[NSMutableDictionary alloc] init];
    rectsInProgress = [[NSMutableDictionary alloc] init];

      completeLines = [[NSMutableArray alloc] init];
      
      [self setBackgroundColor:[UIColor whiteColor]];
      
      [self setMultipleTouchEnabled:YES];
    

// UIPinchGestureRecognizer *pinchRecognizer = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(drawCircle:)];
//
// [self addGestureRecognizer:pinchRecognizer];

}
return self;

}

  • (void)drawRect:(CGRect)rect
    {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 20.0);
    CGContextSetLineCap(context, kCGLineCapRound);

    for (Line *line in [[LineStore sharedStore] allLines]) {
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x, [line end].y);

      float angle = atan2f( ([line begin].y - [line end].y), ([line end].x - [line begin].x) ) * 180/M_PI;
      
      if ((([line end].x - [line begin].x) < 0 && ([line begin].y - [line end].y) < 0) ||
          (([line end].x - [line begin].x) >= 0 && ([line begin].y - [line end].y) < 0) ) {
          angle = angle + 360;
      }
    
      [[UIColor colorWithHue:angle/360 saturation:1.0 brightness:1.0 alpha:1.0] setStroke];
      
      CGContextStrokePath(context);
    

    }

    for (NSValue *v in linesInProgress) {
    Line *line = [linesInProgress objectForKey:v];
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x, [line end].y);

      float angle = atan2f( ([line begin].y - [line end].y), ([line end].x - [line begin].x) ) * 180/M_PI;
      
      if ( (([line end].x - [line begin].x) < 0 && ([line begin].y - [line end].y) < 0) || (([line end].x - [line begin].x) >= 0 && ([line begin].y - [line end].y) < 0) ) {
          angle = angle+360;
      }
      
      //        NSLog(@"Angle is: %f", angle);
      
      [[UIColor colorWithHue:angle/360 saturation:1.0 brightness:1.0 alpha:1.0] setStroke];
      
      CGContextStrokePath(context);
    

    }

    for (int i = 0; i < [[[RetanguloStore sharedStore] allLines] count]; i=i+2) {

      Line *componentA = [[[RetanguloStore sharedStore] allLines] objectAtIndex:i];
      Line *componentB = [[[RetanguloStore sharedStore] allLines] objectAtIndex:i+1];
      
      CGPoint origin = CGPointMake( MIN([componentA end].x, [componentB end].x), MIN( [componentA end].y, [componentB end].y) );
      CGSize size = CGSizeMake((MAX([componentA end].x, [componentB end].x) - MIN([componentA end].x, [componentB end].x)),
                               (MAX([componentA end].y, [componentB end].y) - MIN([componentA end].y, [componentB end].y)));
      
      CGRect rect = CGRectMake(origin.x, origin.y, size.width, size.height);
      
      CGContextSetLineWidth(context, 5.0);
      [[UIColor grayColor] setStroke];
      
      CGContextMoveToPoint(context, origin.x, origin.y);
      CGContextStrokeEllipseInRect(context, rect);
    

    }

    for (NSValue *e in rectsInProgress) {

      NSMutableDictionary *components = [rectsInProgress objectForKey:e];
      
      if ([components count] > 0) {
      
          NSMutableArray *sortedComponents = [[NSMutableArray alloc] init];
          
          for (Line *c in components) {
              
              [sortedComponents addObject:[components objectForKey:c]];
              
          }
          
          Line *componentA = [sortedComponents objectAtIndex:0];
          Line *componentB = [sortedComponents objectAtIndex:1];
    
          
          CGPoint origin = CGPointMake( MIN([componentA end].x, [componentB end].x), MIN( [componentA end].y, [componentB end].y) );
          CGSize size = CGSizeMake((MAX([componentA end].x, [componentB end].x) - MIN([componentA end].x, [componentB end].x)),
                                   (MAX([componentA end].y, [componentB end].y) - MIN([componentA end].y, [componentB end].y)));
          
          CGRect rect = CGRectMake(origin.x, origin.y, size.width, size.height);
          
          CGContextSetLineWidth(context, 5.0);
          [[UIColor redColor] setStroke];
          
          CGContextMoveToPoint(context, origin.x, origin.y);
          CGContextStrokeEllipseInRect(context, rect);
      }
    

    }

}

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
    if ([touches count] == 2) {
    // NSLog(@“touch: %@”, t);
    // NSLog(@“event: %@”, event);

      NSValue *eventKey = [NSValue valueWithNonretainedObject:event];
      
      NSMutableDictionary *components = [[NSMutableDictionary alloc] init];
      
      for (UITouch *c in touches) {
    
          NSValue *componentKey = [NSValue valueWithNonretainedObject:c];
          
          CGPoint loc = [c locationInView:self];
          Line *newComponent = [[Line alloc] init];
          [newComponent setBegin:loc];
          [newComponent setEnd:loc];
          
          [components setObject:newComponent forKey:componentKey];
      }
      
      [rectsInProgress setObject:components forKey];
      NSLog(@"New rects: %@", [rectsInProgress objectForKey]);
    

    } else {

      for (UITouch *t in touches) {
          // Is this a double tap?
          if ([t tapCount] > 4) {
              [self clearAll];
              return;
          }
          
          // User the touch object (packed in a NSValue as the key
          NSValue *key = [NSValue valueWithNonretainedObject:t];
          
          // Create a line for the value
          CGPoint loc = [t locationInView:self];
          Line *newLine = [[Line alloc] init];
          [newLine setBegin:loc];
          [newLine setEnd:loc];
          
          // Put pair in dictionary
          [linesInProgress setObject:newLine forKey:key];
      }
    

    }
    }

  • (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {

    // Update linesInProcess with moved touches
    for (UITouch *t in touches) {

      if ([ [rectsInProgress objectForKey:[NSValue valueWithNonretainedObject:event]] objectForKey:[NSValue valueWithNonretainedObject:t] ]) {
          
          Line *component = [ [rectsInProgress objectForKey:[NSValue valueWithNonretainedObject:event]] objectForKey: [NSValue valueWithNonretainedObject:t]];
          
          CGPoint loc = [t locationInView:self];
          [component setEnd:loc];
          
          NSLog(@"Rects moved: %@", [[rectsInProgress objectForKey:[NSValue valueWithNonretainedObject:event]] objectForKey:[NSValue valueWithNonretainedObject:t]]);
    
      } else {
          
          NSValue *key = [NSValue valueWithNonretainedObject:t];
          
          // Find the line for this touch
          Line *line = [linesInProgress objectForKey:key];
          
          // Update the line
          CGPoint loc = [t locationInView:self];
          [line setEnd:loc];
      }
    

    }
    [self setNeedsDisplay];
    }

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

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

  • (void)endTouches:(NSSet *)touches withEvent:(UIEvent *)event
    {
    for (UITouch *t in touches) {

      if ([ [rectsInProgress objectForKey:[NSValue valueWithNonretainedObject:event]] objectForKey:[NSValue valueWithNonretainedObject:t] ]) {
          
          NSLog(@"touches count: %d", [touches count]);
          
          Line *component = [ [rectsInProgress objectForKey:[NSValue valueWithNonretainedObject:event]] objectForKey:[NSValue valueWithNonretainedObject:t]];
          
          if (component) {
          
              [[RetanguloStore sharedStore] addLine:component];
              [[rectsInProgress objectForKey:[NSValue valueWithNonretainedObject:event]] removeObjectForKey:[NSValue valueWithNonretainedObject:t]];
              
              NSLog(@"Saved components (%d): %@", [[[RetanguloStore sharedStore] allLines] count], [[RetanguloStore sharedStore] allLines]);
          }
          
      } else {
          
          NSValue *key = [NSValue valueWithNonretainedObject:t];
          
          Line *line = [linesInProgress objectForKey:key];
          
          if (line) {
              [[LineStore sharedStore] addLine:line];
              [linesInProgress removeObjectForKey:key];
          }
      }
    

    }
    [self setNeedsDisplay];
    }

  • (void)clearAll
    {
    // Clear the collections
    [linesInProgress removeAllObjects];
    [rectsInProgress removeAllObjects];

    [[LineStore sharedStore] removeAllLines];
    [[RetanguloStore sharedStore] removeAllLines];

    // Redraw
    [self setNeedsDisplay];
    }

@end[/code]


#5

Thanks for your comments, Plastic. I’m travelling at the moment and won’t have time fully reply, but will do so when I have some time. I considered trying to implement ellipses but decided to go with a strict interpretation of “circle” to only mean circles and not an ellipse. I’ll look at this soon.

Thanks for the positive comments. I’m an amateur when it comes to programming and have never taken a formal course but have fallen in love with the process and the joy when I finally get something to work.

I haven’t tested any code on an actual device. I know I need to and will have to eventually.

Jeff


#6

I created two classes

TouchCircle

@interface TouchCircle : NSObject <NSCoding>
{
    NSMutableArray *touchPoints;
}

@property (nonatomic, getter = isTouchEnd)BOOL touchEnd;

- (void)addTouchPoint:(TouchPoint *)tp;
- (void)updateTouchPoint:(TouchPoint *)tp;
- (void)touchPointEnd:(TouchPoint *)tp;

- (CGPoint)getPointOf:(int)i;

@end

TouchCircle.m

[code]- (BOOL)isTouchEnd
{
BOOL a = [[touchPoints objectAtIndex:0] isTouchEnd];
BOOL b = [[touchPoints objectAtIndex:1] isTouchEnd];
return a && b;
}

  • (void)addTouchPoint:(TouchPoint *)tp
    {
    [touchPoints addObject:tp];
    }
  • (void)updateTouchPoint:(TouchPoint *)tp
    {
    for (TouchPoint *touchPoint in touchPoints) {
    if ([[tp key] isEqualToValue:[touchPoint key]]) {
    [touchPoint setPoint:[tp point]];
    return;
    }
    }
    }
  • (void)touchPointEnd:(TouchPoint *)tp
    {
    for (int i = 0; i < [touchPoints count]; i++) {
    TouchPoint *touchpoint = [touchPoints objectAtIndex:i];
    if ([[tp key] isEqualToValue:[touchpoint key]]) {
    [touchpoint setTouchEnd:YES];
    return;
    }
    }
    }
  • (CGPoint)getPointOf:(int)i
    {
    return [[touchPoints objectAtIndex:i] point];
    }
    [/code]
    TouchPoint

[code]@interface TouchPoint : NSObject

@property (nonatomic) NSValue *key;
@property (nonatomic) CGPoint point;
@property (nonatomic, getter = isTouchEnd)BOOL touchEnd;

@end
[/code]
I added the following snippets to TouchDrawView

initWithFrame:

circlesInProcess = [[NSMutableDictionary alloc] init]; completeCircles = [[NSMutableArray alloc] init];
drawRect:

[[UIColor blackColor] set]; for (TouchCircle *tc in completeCircles) { CGPoint a = [tc getPointOf:0]; CGPoint b = [tc getPointOf:1]; float beginx = MIN(a.x, b.x); float beginy = MIN(a.y, b.y); float width = fabsf(a.x - b.x); float height = fabsf(a.y - b.y); CGRect circleRect = CGRectMake(beginx, beginy, width, height); CGContextStrokeEllipseInRect(context, circleRect); } [[UIColor redColor] set]; NSArray *circles = [circlesInProcess allValues]; for (TouchCircle *tc in circles) { CGPoint a = [tc getPointOf:0]; CGPoint b = [tc getPointOf:1]; float beginx = MIN(a.x, b.x); float beginy = MIN(a.y, b.y); float width = fabsf(a.x - b.x); float height = fabsf(a.y - b.y); CGRect circleRect = CGRectMake(beginx, beginy, width, height); CGContextStrokeEllipseInRect(context, circleRect); }
touchesBegan:

[code] if ([linesInProcess count] == 2) {
TouchCircle *tc = [[TouchCircle alloc] init];

        NSArray *keys = [linesInProcess allKeys];
        for (NSValue *k in keys) {
            Line *line = [linesInProcess objectForKey:k];
            CGPoint p = [line end];
            
            TouchPoint *tp = [[TouchPoint alloc] init];
            [tp setKey:k];
            [tp setPoint:p];
            
            [tc addTouchPoint:tp];
        }
        for (NSValue *k in keys) {
            [circlesInProcess setObject:tc forKey:k];   // adds 2 keys per circle
        }
        [linesInProcess removeAllObjects];
    } 

[/code]
touchesMoved:

TouchCircle *tc = [circlesInProcess objectForKey:key]; if (tc) { TouchPoint *tp = [[TouchPoint alloc] init]; [tp setKey:key]; [tp setPoint:[t locationInView:self]]; [tc updateTouchPoint:tp]; }
endTouches:

[code] TouchCircle *tc = [circlesInProcess objectForKey:key];
if (tc) {
TouchPoint *tp = [[TouchPoint alloc] init];
[tp setKey:key];
[tc touchPointEnd:tp];
if ([tc isTouchEnd]) {
[completeCircles addObject:tc];
[circlesInProcess removeObjectsForKeys:[circlesInProcess allKeysForObject:tc]];
}

    }

[/code]
clearAll:

[circlesInProcess removeAllObjects]; [completeCircles removeAllObjects];
I’m able to draw multiple circles at the same time (and no crashing taking off one finger!)

Please comment. Thanks.
Paul


#7

Here’s my solution using the same Line class

TouchDrawView.h

[code]@interface TouchDrawView : UIView
{
NSMutableDictionary *linesInProcess;
NSMutableDictionary *circleInProcess;
}

@property (nonatomic) NSMutableArray *completeLines;
@property (nonatomic) NSMutableArray *completeCircles;
@property (nonatomic) NSValue *key;

  • (void)clearAllLines;
  • (void)clearAllCircles;
  • (void)endTouches:(NSSet *)touches;

@end
[/code]

TouchDrawView.m

[code]#import “TouchDrawView.h”
#import “Line.h”

@implementation TouchDrawView

@synthesize completeLines;
@synthesize completeCircles;
@synthesize key;

  • (id)initWithFrame:(CGRect)r
    {
    self = [super initWithFrame:r];

    if (self) {
    linesInProcess = [NSMutableDictionary dictionary];
    circleInProcess = [NSMutableDictionary dictionary];

      NSString *linePath = @"~/Documents/lines.archive";
      linePath = [linePath stringByExpandingTildeInPath];
      completeLines = [NSKeyedUnarchiver unarchiveObjectWithFile];
      
      NSString *circlePath = @"~/Documents/circles.archive";
      circlePath = [circlePath stringByExpandingTildeInPath];
      completeCircles = [NSKeyedUnarchiver unarchiveObjectWithFile:circlePath];
      
      if (!completeLines) {
          completeLines = [NSMutableArray array];
      }
      
      if (!completeCircles) {
          completeCircles = [NSMutableArray array];
      }
      
      [self setBackgroundColor:[UIColor whiteColor]];
      
      [self setMultipleTouchEnabled:YES];
    

    }

    return self;
    }

  • (void)drawRect:(CGRect)rect
    {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 10.0);
    CGContextSetLineCap(context, kCGLineCapRound);

    for (Line *line in completeLines) {
    CGContextMoveToPoint(context, line.begin.x, line.begin.y);
    CGContextAddLineToPoint(context, line.end.x, line.end.y);

      if (line.begin.y > line.end.y) {
          [[UIColor blackColor] set];
      } else {
          [[UIColor blueColor] set];
      }
      
      CGContextStrokePath(context);
    

    }

    // Draw lines in process in red
    [[UIColor redColor] set];
    for (NSValue *v in linesInProcess) {
    Line *line = [linesInProcess objectForKey:v];
    CGContextMoveToPoint(context, line.begin.x, line.begin.y);
    CGContextAddLineToPoint(context, line.end.x, line.end.y);
    CGContextStrokePath(context);
    }

    // Draw complete circles in black
    [[UIColor blackColor] set];
    for (Line *circle in completeCircles) {
    float x = circle.begin.x + (circle.end.x - circle.begin.x)/2;
    float y = circle.begin.y + (circle.end.y - circle.begin.y)/2;
    CGFloat radius = hypotf(circle.begin.x - circle.end.x, circle.begin.y - circle.end.y)/2;

      CGContextBeginPath(context);
      CGContextAddArc(context, x, y, radius, 0, 2 * M_PI, 0);
      CGContextStrokePath(context);
    

    }

    // Draw circle in process in red
    [[UIColor redColor] set];
    for (NSValue *v in circleInProcess) {
    Line *circle = [circleInProcess objectForKey:key];
    float x = circle.begin.x + (circle.end.x - circle.begin.x)/2;
    float y = circle.begin.y + (circle.end.y - circle.begin.y)/2;
    CGFloat radius = hypotf(circle.begin.x - circle.end.x, circle.begin.y - circle.end.y)/2;

      CGContextBeginPath(context);
      CGContextAddArc(context, x, y, radius, 0, 2 * M_PI, 0);
      CGContextStrokePath(context);
    

    }

}

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
    int i = 0;
    for (UITouch *t in touches) {
    i++;

      switch (i) {
          case 1:{
              // Double tap erases everything
              if (t.tapCount > 1) {
                  [self clearAllLines];
                  [self clearAllCircles];
                  return;
              }
              
              // Use the touch object as the key
              key = [NSValue valueWithNonretainedObject:t];
              
              // Create a line for the value
              CGPoint loc = [t locationInView:self];
              Line *newLine = [[Line alloc] init];
              [newLine setBegin:loc];
              [newLine setEnd:loc];
              
              // Put pair in dictionary
              [linesInProcess setObject:newLine forKey:key];
          }
              break;
          
          case 2:{
              CGPoint loc = [t locationInView:self];
              
              // This touch will make a circle
              Line *newCircle = [[Line alloc] init];
              // Get begin property from the previously made line
              [newCircle setBegin:[[linesInProcess objectForKey:key] begin]];
              // Set end property to current point
              [newCircle setEnd:loc];
              
              // Remove line and save circle
              [linesInProcess removeObjectForKey:key];
              [circleInProcess setObject:newCircle forKey:key];
          }
              break;
              
          default:
              break;
      }
    

    }
    }

  • (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
    {
    int i = 0;
    // Update linesInProcess with moved touches
    for (UITouch *t in touches) {
    i++;

      switch (i) {
          case 1:{
              // Find the line for this touch
              Line *line = [linesInProcess objectForKey:key];
              
              if (line) {
                  // Update the line
                  CGPoint loc = [t locationInView:self];
                  [line setEnd:loc];
              } else {
                  // Find the circle for this touch
                  Line *circle = [circleInProcess objectForKey:key];
                  CGPoint loc = [t locationInView:self];
                  [circle setBegin:loc];
              }
          }
              break;
              
          case 2:{
              Line *circle = [circleInProcess objectForKey:key];
              CGPoint loc = [t locationInView:self];
              [circle setEnd:loc];
          }
              break;
              
          default:
              break;
      }
    

    }

    // Redraw
    [self setNeedsDisplay];
    }

  • (void)endTouches:(NSSet *)touches
    {
    // Remove touches from dictionary
    for (UITouch *t in touches) {
    Line *line = [linesInProcess objectForKey:key];
    Line *circle = [circleInProcess objectForKey:key];

      // If this is a double tap, 'line' will be nil, so make sure not to add it to the array
      if (line) {
          [completeLines addObject:line];
          [linesInProcess removeObjectForKey:key];
      } else if (circle) {
          [completeCircles addObject:circle];
          [circleInProcess removeObjectForKey:key];
      }
    
      // Redraw
      [self setNeedsDisplay];
    

    }
    }

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

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

  • (void)clearAllLines
    {
    // Clear the collections
    [linesInProcess removeAllObjects];
    [completeLines removeAllObjects];;

    // Redraw
    [self setNeedsDisplay];
    }

  • (void)clearAllCircles
    {
    [circleInProcess removeAllObjects];
    [completeCircles removeAllObjects];

    [self setNeedsDisplay];
    }

@end
[/code]

By the way, does anyone know why the for enumeration was necessary?