Another solution to Gold Challenge

#1

Following is my solution to the Gold challenge:

BEWARE: My program is only capable of drawing circles. I removed the “line-drawing” code for the sake of simplicity.

First of all, I created a BNRCircle class. Here’s BNRCircle.h:

#import <UIKit/UIKit.h>

@interface BNRCircle : NSObject

@property (nonatomic) CGPoint startingPoint;
@property (nonatomic) CGPoint endPoint;

@property (nonatomic, readonly) CGPoint center;
@property (nonatomic, readonly) CGFloat radius;

@end

And here’s BNRCircle.m:

#import "BNRCircle.h"

@implementation BNRCircle

@synthesize center = _center;
@synthesize radius = _radius;

- (CGPoint)center
{
    CGFloat x = (self.startingPoint.x + self.endPoint.x) / 2;
    CGFloat y = (self.startingPoint.y + self.endPoint.y) / 2;
    return CGPointMake(x, y);
}

- (CGFloat)radius
{
    CGFloat deltaX = self.endPoint.x - self.startingPoint.x;
    CGFloat deltaY = self.endPoint.y - self.startingPoint.y;
    CGFloat distance = sqrt(deltaX * deltaX + deltaY * deltaY);
    return (distance / 2) / sqrt(2);
}

@end

I add four properties to BNRCircle: startingPoint, endPoint, center and radius. startingPoint and endPoint are defined by the location of the user’s touches on the screen. center and radius are calculated on demand whenever a caller needs them (later on, BNRDrawView will use those two values to draw circles).

The rest of the solution is contained within the BNRDrawView class. BNRDrawView.h remains quite simple:

#import <UIKit/UIKit.h>

@interface BNRDrawView : UIView

@end

While BNRDrawView.m contains most of the logic:

#import "BNRDrawView.h"
#import "BNRLine.h"
#import "BNRCircle.h"

const int CIRCLE = 0;
const int POINT = 1;
NSString * const kLabelStartingPoint = @"STARTING_POINT";
NSString * const kLabelEndPoint = @"END_POINT";

@interface BNRDrawView ()

@property (nonatomic, strong) NSMutableArray *currentTouches;
@property (nonatomic, strong) NSMutableDictionary *circlesInProgress;
@property (nonatomic, strong) NSMutableArray *finishedCircles;

@end

@implementation BNRDrawView

# pragma mark - View life cycle

- (instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame]) {
        self.currentTouches = [[NSMutableArray alloc] init];
        self.circlesInProgress = [[NSMutableDictionary alloc] init];
        self.finishedCircles = [[NSMutableArray alloc] init];
        
        self.backgroundColor = [UIColor grayColor];
        self.multipleTouchEnabled = YES;
    }
    return self;
}

# pragma mark - Touch events

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // Let's put in a log statement to see the order of events
    NSLog(@"%@", NSStringFromSelector(_cmd));
    
    for (UITouch *t in touches) {
        [self.currentTouches addObject:t];
        
        NSInteger touchCount = self.currentTouches.count;
        if (touchCount % 2) {
            // do nothing; another touch is needed to define a circle
        } else {
            // we have an even number of touches; we can define a circle now
            BNRCircle *circle = [[BNRCircle alloc] init];
            
            UITouch *startingTouch = self.currentTouches[touchCount - 2];
            NSValue *startingTouchRef = [NSValue valueWithNonretainedObject:startingTouch];
            circle.startingPoint = [startingTouch locationInView:self];
            
            UITouch *endTouch = self.currentTouches[touchCount - 1];
            NSValue *endTouchRef = [NSValue valueWithNonretainedObject:endTouch];
            circle.endPoint = [endTouch locationInView:self];

            self.circlesInProgress[startingTouchRef] = @[circle, kLabelStartingPoint];
            self.circlesInProgress[endTouchRef] = @[circle, kLabelEndPoint];
        }
        
        [self setNeedsDisplay];
    }
}

- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // Let's put in a log statement to see the order of events
    NSLog(@"%@", NSStringFromSelector(_cmd));
    
    for (UITouch *t in touches) {
        NSValue *tRef = [NSValue valueWithNonretainedObject:t];
        BNRCircle *circle = self.circlesInProgress[tRef][CIRCLE];
        NSString *label = self.circlesInProgress[tRef][POINT];
        
        if ([label isEqualToString:kLabelStartingPoint]) {
            circle.startingPoint = [t locationInView:self];
        } else {
            circle.endPoint = [t locationInView:self];
        }
    }
    
    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    // Let's put in a log statement to see the order of events
    NSLog(@"%@", NSStringFromSelector(_cmd));
    
    for (UITouch *t in touches) {
        NSValue *tRef = [NSValue valueWithNonretainedObject:t];
        
        [self.currentTouches removeObject:t];
        NSInteger touchCount = self.currentTouches.count;
        
        if (touchCount % 2 == 0) {
            // there's an even number of touches left: one circle has been finalized
            BNRCircle *circle = self.circlesInProgress[tRef][CIRCLE];
            if (circle) {
                [self.finishedCircles addObject:circle];
            }
        }
        
        [self.circlesInProgress removeObjectForKey:tRef];
        [self setNeedsDisplay];
    }
}

# pragma mark - Circle drawing

- (void)strokeCircle:(BNRCircle *)circle
{
    UIBezierPath *bp = [UIBezierPath bezierPath];
    bp.lineWidth = 10;
    
    [bp addArcWithCenter:circle.center
                  radius:circle.radius
              startAngle:0.0
                endAngle:M_PI * 2.0
               clockwise:YES];
    [bp stroke];
}

- (void)drawRect:(CGRect)rect
{
    // Draw finished circles in black
    [[UIColor blackColor] set];
    for (BNRCircle *circle in self.finishedCircles) {
        [self strokeCircle:circle];
    }
    
    // Draw circles in progress in red
    [[UIColor redColor] set];
    for (NSValue *tRef in self.circlesInProgress) {
        BNRCircle *circle = self.circlesInProgress[tRef][CIRCLE];
        [self strokeCircle:circle];
    }
}

@end

Current touches on screen are tracked via the currentTouches array.

Circles in progress are tracked via the circlesInProgress dictionary: circlesInProgress is a dictionary that maps the NSValue of a UITouch to an array composed of two elements: the circle partially defined by that touch, and a string constant indicating which of the two bounding points is the touch related to.