All Solutions


#1

Hi there,
solved the challenges today:

[size=130]Bronze:[/size]
First I added a new UIGestureRecognizer as the challenge requested:

TouchDrawView.m 's initWithFrame:

[...]  
        UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                              action:@selector(doubleTap:)];
        [doubleTapRecognizer setNumberOfTapsRequired:2];
        [self addGestureRecognizer:doubleTapRecognizer];
[...]        

Now you only need to implement the doubleTap: Method and you’re done:

- (void)doubleTap:(UITapGestureRecognizer *)gr {
    [self clearAll];
    [self setNeedsDisplay];
}

[size=130]Silver:[/size]
There are basically two options you got. Both of them are pretty easy. The question is: What do you want to happen when a line is selected and the user starts moving his finger on the screen.

Version 1:
You want to unselect the currently selected line and draw the new line:
Add this to TouchViewController.m’s “touchesBegan: withEvent:” method:

if([self selectedLine]) { [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES]; [self setSelectedLine:nil]; }

Version 2:
You don’t want a new line. You want the selected line to get dragged:
Add the following if-statement around the code in TouchViewController.m’s “touchesBegan: withEvent:” method:

if (![self selectedLine]) { //The normal touchesBegan:withEvent: code }

[size=130]Gold:[/size]
Now you got two options again:
Do you want the highest speed recognized over all the drawing time or just the highest speed of the last few movements to be the thickness ?
No matter which option you prefer - add a thickness property to the Line class synthesize the new property. I’ll not post the code for this - you know how to declare a property and synthesize it (in my case it’s named “thickness” and is a CGFloat).

Now I declared the speed in “TouchDrawView.h”:


@interface TouchDrawView : UIView <UIGestureRecognizerDelegate> {
    NSMutableDictionary *linesInProcess;
    NSMutableArray *completeLines;
    
    UIPanGestureRecognizer *moveRecognizer;
    
    CGPoint speed;
}[/code]

Why CGPoint ? Because we got the speed in X- and in Y-direction.

And here comes the exciting part - "[b]TouchDrawView.m[/b]":
[code]
//
//  TouchDrawView.m
//  TouchTracker
//
//  Created by Joerg Ki****** on 29.08.12.
//  Copyright (c) 2012 Joerg Ki******. All rights reserved.
//

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

#define HIGHEST_SPEED_OVERALL 1

@implementation TouchDrawView
@synthesize selectedLine;

- (id)initWithFrame:(CGRect)r {
    self = [super initWithFrame:r];
    if (self) {
        linesInProcess = [[NSMutableDictionary alloc] init];
        completeLines = [[NSMutableArray alloc] init];
        
        [self setBackgroundColor:[UIColor whiteColor]];
        [self setMultipleTouchEnabled:YES];
        
        UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                        action:@selector(tap:)];
        [self addGestureRecognizer:tapRecognizer];
        
        UILongPressGestureRecognizer *pressRecognizer =
            [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                                          action:@selector(longPress:)];
        [self addGestureRecognizer:pressRecognizer];
        
        moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLine:)];
        [moveRecognizer setDelegate:self];
        [moveRecognizer setCancelsTouchesInView:NO];
        [self addGestureRecognizer:moveRecognizer];
    }
    return self;
}

#pragma mark ----------------------------< GESTURES >---------------------------------

- (void)tap:(UIGestureRecognizer *)gr {
    NSLog(@"Recognized tap.");
    
    CGPoint point = [gr locationInView:self];
    [self setSelectedLine:[self lineAtPoint:point]];
    
    //If we just tapped, remove all alines in process
    //so that a tap doesn't result in a new line
    [linesInProcess removeAllObjects];
    
    if ([self selectedLine]) {
        [self becomeFirstResponder];
        
        //Grab the menu controller
        UIMenuController *menu = [UIMenuController sharedMenuController];
        
        //Create a new "Delete" UIMenuItem
        UIMenuItem *deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete"
                                                            action:@selector(deleteLine:)];
        [menu setMenuItems:[NSArray arrayWithObject:deleteItem]];
        
        //Tell the mue where it should come from and show it
        [menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];
        [menu setMenuVisible:YES animated:YES];
    } else {
        //Hide the menu if no line is selected
        [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
    }
    
    [self setNeedsDisplay];
}

- (void)longPress:(UIGestureRecognizer *)gr {
    if ([gr state] == UIGestureRecognizerStateBegan) {
        CGPoint point = [gr locationInView:self];
        [self setSelectedLine:[self lineAtPoint:point]];
        
        if ([self selectedLine]) {
            [linesInProcess removeAllObjects];
        }
    } else if ([gr state] == UIGestureRecognizerStateEnded) {
        [self setSelectedLine:nil];
    }
    [self setNeedsDisplay];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if (gestureRecognizer == moveRecognizer) {
        return YES;
    }
    return NO;
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (void)moveLine:(UIPanGestureRecognizer *)gr {
    //If we haven't selected a line, we don't do anything here
    if (![self selectedLine]) {
        
#if (HIGHEST_SPEED_OVERALL == 0)
        if ([gr velocityInView:self].x) {
            speed.x = [gr velocityInView:self].x;
        }
        
        if ([gr velocityInView:self].y) {
            speed.y = [gr velocityInView:self].y;
        }
#else 
        if ([gr velocityInView:self].x > speed.x) {
            speed.x = [gr velocityInView:self].x;
        }
        
        if ([gr velocityInView:self].y > speed.y) {
            speed.y = [gr velocityInView:self].y;
        }
#endif
        NSArray *keys = [[NSArray alloc] initWithArray:[linesInProcess allKeys]];
#if (HIGHEST_SPEED_OVERALL == 0)
        static NSMutableArray *lastTenValues = nil;
        if (lastTenValues == nil) {
            lastTenValues = [[NSMutableArray alloc] init];
        }
        
        if (fabsf(speed.x) > fabsf(speed.y)) {
            [lastTenValues addObject:[NSNumber numberWithFloat:fabs(speed.x)]];
        } else {
            [lastTenValues addObject:[NSNumber numberWithFloat:fabsf(speed.y)]];
        }
        
        while ([lastTenValues count] > 10) {
            [lastTenValues removeObjectAtIndex:0];
        }
        
        float highestOfLastTenValues = 0.0;
        int j = 0;
        for (NSNumber *n in lastTenValues) {
            if ([n floatValue] > highestOfLastTenValues) {
                highestOfLastTenValues = [n floatValue];
            }
            j++;
            NSLog(@"Object %d == %f", j, [n floatValue]);
        }
#endif
        
        for (int i = 0; i < [linesInProcess count]; i++) {
            
#if (HIGHEST_SPEED_OVERALL == 1)
            if (fabsf(speed.x) > fabsf(speed.y)) {
                [[linesInProcess objectForKey:[keys objectAtIndex:i]] setThickness:fabsf(speed.x / 30.0)];
            } else {
                [[linesInProcess objectForKey:[keys objectAtIndex:i]] setThickness:fabsf(speed.y / 30.0)];
            }
#else
            [[linesInProcess objectForKey:[keys objectAtIndex:i]] setThickness:highestOfLastTenValues / 30.0];
#endif
        }
    }
    
    //When the pan recognizer changes its position...
    if ([gr state] == UIGestureRecognizerStateChanged) {
        //How far has the pan moved?
        CGPoint translation = [gr translationInView:self];
        
        //Add the translation to the current begin and end point of the line
        CGPoint begin = [[self selectedLine] begin];
        CGPoint end = [[self selectedLine] end];
        begin.x += translation.x;
        begin.y += translation.y;
        end.x += translation.x;
        end.y += translation.y;
        
        //Set the new beginning and end points of the line
        [[self selectedLine] setBegin:begin];
        [[self selectedLine] setEnd:end];
        
        //Redraw the screen
        [self setNeedsDisplay];
        
        [gr setTranslation:CGPointZero inView:self];
    }
}

- (void)deleteLine:(id)sender {
    //Remove the selected line from the list of completeLines
    [completeLines removeObject:[self selectedLine]];
    
    //Redraw everything
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(context, 10.0);
    CGContextSetLineCap(context, kCGLineCapRound);
    
    //Draw complete lines in black
    [[UIColor blackColor] set];
    for (Line *line in completeLines) {
        CGContextSetLineWidth(context, [line thickness]);
        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 linesInProcess) {
        Line *line = [linesInProcess objectForKey:v];
        CGContextSetLineWidth(context, [line thickness]);
        CGContextMoveToPoint(context, [line begin].x, [line begin].y);
        CGContextAddLineToPoint(context, [line end].x, [line end].y);
        CGContextStrokePath(context);
    }
    
    //If there is a selected line, draw it
    if ([self selectedLine]) {
        [[UIColor greenColor] set];
        CGContextSetLineWidth(context, [[self selectedLine] thickness]);
        CGContextMoveToPoint(context, [[self selectedLine] begin].x, [[self selectedLine] begin].y);
        CGContextAddLineToPoint(context, [[self selectedLine] end].x, [[self selectedLine] end].y);
        CGContextStrokePath(context);
    }
}

- (void)clearAll {
    //Clear the collections
    [linesInProcess removeAllObjects];
    [completeLines removeAllObjects];
    
    //Redraw
    [self setNeedsDisplay];
}

- (Line *)lineAtPoint:(CGPoint)p {
    //Find a line close to p
    for (Line *l in completeLines) {
        CGPoint start = [l begin];
        CGPoint end = [l end];
    
        //Check a few points on the line
        for (float t = 0.0; t <= 1.0; t+=0.05) {
            float x = start.x + t * (end.x - start.x);
            float y = start.y + t * (end.y - start.y);
            
            //If the tapped point is within 20 points, let's return this line
            if (hypot(x - p.x, y- p.y) < [l thickness] / 1.5)
                return l;
        }
    }
    
    //if nothing is close enough to the tapped point, then we didn't select a line
    return nil;
}

# pragma mark -------------------------< TOUCH METHODS >-----------------------------

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    speed.x = 0;
    speed.y = 0;
    for (UITouch *t in touches) {
        //Is this a double tap ?
        if ([t tapCount] > 1) {
            [self clearAll];
            return;
        }
        
        //Use the touch object (packed in an 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
        [linesInProcess setObject:newLine forKey:key];
    }
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    //Update linesInProcess with moved touches
    for (UITouch *t in touches) {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        
        //Find the line for this touch
        Line *line = [linesInProcess objectForKey:key];
        
        //Update the line
        CGPoint loc = [t locationInView:self];
        [line setEnd:loc];
    }
    
    //Redraw
    [self setNeedsDisplay];
}

- (void)endTouches:(NSSet *)touches {
    //Remove editing touches from dictionary
    for (UITouch *t in touches) {
        NSValue *key = [NSValue valueWithNonretainedObject:t];
        Line *line = [linesInProcess 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];
        }
    }
    //Redraw
    [self setNeedsDisplay];
}

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

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

@end

[/code]

Try out replacing the 1 with a 0 in the first line 
[code]#define HIGHEST_SPEED_OVERALL 1[/code]
This will let you change between the two options mentioned above.

[b][size=130]Mega - Gold:[/size][/b]
I won't explain it in detail now (but feel free to ask for details if you don't get everything) but these are the basics:
First I made two UISwipeGestureRecognizers (one for swiping upwards and one for sw. downwards).
Then I created a "ColorPicker"-View-Object.
After that "Line" got its new color property.
Now I connected the ColorPicker and the TouchDrawView. I know - that wasn't the most efficient way but it let me practice that "let a view send messages to its controller"-stuff Joe talked about some chapters ago.

Anyway. Here are the Files:

[b]Line.h[/b]
[code]//
//  Line.h
//  TouchTracker
//
//  Created by Christian Ki****** on 29.08.12.
//  Copyright (c) 2012 Jörg Ki******. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Line : NSObject

@property (nonatomic) CGPoint begin;
@property (nonatomic) CGPoint end;
@property (nonatomic) UIColor *color;

@end

Line.m

[code]//
// Line.m
// TouchTracker
//
// Created by Christian Ki****** on 29.08.12.
// Copyright © 2012 Jörg Ki******. All rights reserved.
//

#import “Line.h”

@implementation Line

@synthesize begin, end, color;

  • (id)init
    {
    self = [super init];
    if (self) {
    color = nil;
    }
    return self;
    }

@end
[/code]

ColorPicker.h

[code]//
// ColorPicker.h
// TouchTracker
//
// Created by Christian Ki****** on 31.08.12.
// Copyright © 2012 Jörg Ki******. All rights reserved.
//

#import <UIKit/UIKit.h>

@class TouchViewController;

@interface ColorPicker : UIView {

UISegmentedControl *colorPickSegCtrl;

}

@property (nonatomic) TouchViewController *controller;
@property (nonatomic) UIColor *selectedColor;

  • (UIView *)view;
  • (UISegmentedControl *)colorPickSegCtrl;

@end
[/code]

ColorPicker.m

//
//  ColorPicker.m
//  TouchTracker
//
//  Created by Christian Ki****** on 31.08.12.
//  Copyright (c) 2012 Jörg Ki******. All rights reserved.
//

#import "ColorPicker.h"
#import "TouchViewController.h"

@implementation ColorPicker

@synthesize controller, selectedColor;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        UIImage *yellowDot = [[UIImage alloc] init];
        yellowDot = [UIImage imageNamed:@"Yellow.png"];
        
        UIImage *redDot = [[UIImage alloc] init];
        redDot = [UIImage imageNamed:@"Red.png"];
        
        UIImage *greenDot = [[UIImage alloc] init];
        greenDot = [UIImage imageNamed:@"Green.png"];
        
        UIImage *blueDot = [[UIImage alloc] init];
        blueDot = [UIImage imageNamed:@"Blue.png"];
        
        NSArray *colors = [[NSArray alloc] initWithObjects:yellowDot, redDot, greenDot, blueDot, nil];
        
        colorPickSegCtrl = [[UISegmentedControl alloc] initWithItems:colors];
        [colorPickSegCtrl addTarget:self
                             action:@selector(segmentSelected)
                   forControlEvents:UIControlEventValueChanged];
    }
    return self;
}

- (void)segmentSelected {
    NSInteger selected = [colorPickSegCtrl selectedSegmentIndex];
    switch (selected) {
        case 0:
            selectedColor = [UIColor yellowColor];
            break;
        case 1:
            selectedColor = [UIColor redColor];
            break;
        case 2:
            selectedColor = [UIColor greenColor];
            break;
        case 3:
            selectedColor = [UIColor blueColor];
            break;
            
        default:
            NSLog(@"colorPickSegCtrl : unrecognized segment selected");
            break;
    }
    
    [controller setColorForFutureLines];
}

- (UIView *)view {
    return colorPickSegCtrl;
}

- (UISegmentedControl *)colorPickSegCtrl {
    return colorPickSegCtrl;
}

@end

TouchDrawView.h

[code]//
// TouchDrawView.h
// TouchTracker
//
// Created by Christian Ki****** on 29.08.12.
// Copyright © 2012 Jörg Ki******. All rights reserved.
//

#import <Foundation/Foundation.h>

@class Line;
@class TouchViewController;

@interface TouchDrawView : UIView {
NSMutableDictionary *linesInProcess;
NSMutableArray *completeLines;

UIPanGestureRecognizer *moveRecognizer;
UISwipeGestureRecognizer *swipeRecognizerOpenMenu;
UISwipeGestureRecognizer *swipeRecognizerCloseMenu;

}

@property (nonatomic, weak) Line *selectedLine;
@property (nonatomic) TouchViewController *controller;
@property (nonatomic) UIColor *selectedColor;

  • (Line *)lineAtPoint:(CGPoint)p;

  • (void)clearAll;

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

@end
[/code]

TouchDrawView.m

[code]//
// TouchDrawView.m
// TouchTracker
//
// Created by Christian Ki****** on 29.08.12.
// Copyright © 2012 Jörg Ki******. All rights reserved.
//

#import “TouchDrawView.h”
#import “Line.h”
#import “TouchViewController.h”

@implementation TouchDrawView
@synthesize selectedLine, controller, selectedColor;

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

      [self setBackgroundColor:[UIColor whiteColor]];
      [self setMultipleTouchEnabled:YES];
      
      UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                                      action:@selector(tap:)];
      [self addGestureRecognizer:tapRecognizer];
      
      UILongPressGestureRecognizer *pressRecognizer =
          [[UILongPressGestureRecognizer alloc] initWithTarget:self
                                                        action:@selector(longPress:)];
      [self addGestureRecognizer:pressRecognizer];
      
      moveRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(moveLine:)];
      [moveRecognizer setDelegate:self];
      [moveRecognizer setCancelsTouchesInView:NO];
      [self addGestureRecognizer:moveRecognizer];
      
      swipeRecognizerOpenMenu = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                                                                       action:@selector(swipe:)];
      [swipeRecognizerOpenMenu setDelegate:self];
      [swipeRecognizerOpenMenu setDirection:UISwipeGestureRecognizerDirectionUp];
      [swipeRecognizerOpenMenu setCancelsTouchesInView:YES];
      //[swipeRecognizerOpenMenu setNumberOfTouchesRequired:3];
      [self addGestureRecognizer:swipeRecognizerOpenMenu];
      
      swipeRecognizerCloseMenu = [[UISwipeGestureRecognizer alloc] initWithTarget:self
                                                                          action:@selector(swipe:)];
      [swipeRecognizerCloseMenu setDelegate:self];
      [swipeRecognizerCloseMenu setDirection:UISwipeGestureRecognizerDirectionDown];
      [swipeRecognizerCloseMenu setCancelsTouchesInView:YES];
      //[swipeRecognizerCloseMenu setNumberOfTouchesRequired:3];
      [self addGestureRecognizer:swipeRecognizerCloseMenu];
    

    }
    return self;
    }

#pragma mark ----------------------------< GESTURES >---------------------------------

  • (void)tap:(UIGestureRecognizer *)gr {
    NSLog(@“Recognized tap.”);

    CGPoint point = [gr locationInView:self];
    [self setSelectedLine:[self lineAtPoint:point]];

    //If we just tapped, remove all alines in process
    //so that a tap doesn’t result in a new line
    [linesInProcess removeAllObjects];

    if ([self selectedLine]) {
    [self becomeFirstResponder];

      //Grab the menu controller
      UIMenuController *menu = [UIMenuController sharedMenuController];
      
      //Create a new "Delete" UIMenuItem
      UIMenuItem *deleteItem = [[UIMenuItem alloc] initWithTitle:@"Delete"
                                                          action:@selector(deleteLine:)];
      [menu setMenuItems:[NSArray arrayWithObject:deleteItem]];
      
      //Tell the menu where it should come from and show it
      [menu setTargetRect:CGRectMake(point.x, point.y, 2, 2) inView:self];
      [menu setMenuVisible:YES animated:YES];
    

    } else {
    //Hide the menu if no line is selected
    [[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
    }

    [self setNeedsDisplay];
    }

  • (void)longPress:(UIGestureRecognizer *)gr {
    if ([gr state] == UIGestureRecognizerStateBegan) {
    CGPoint point = [gr locationInView:self];
    [self setSelectedLine:[self lineAtPoint:point]];

      if ([self selectedLine]) {
          [linesInProcess removeAllObjects];
      }
    

    } else if ([gr state] == UIGestureRecognizerStateEnded) {
    [self setSelectedLine:nil];
    }
    [self setNeedsDisplay];
    }

  • (void)swipe:(UISwipeGestureRecognizer *)gr {
    [linesInProcess removeAllObjects];

    if ([gr direction] == UISwipeGestureRecognizerDirectionUp) {
    NSLog(@“Swipe up recognized”);
    [controller loadColorPicker];

    }

    if ([gr direction] == UISwipeGestureRecognizerDirectionDown) {
    NSLog(@“Swipe down recognized”);
    [controller unloadColorPicker];
    }

}

  • (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    if (gestureRecognizer == moveRecognizer) {
    return YES;
    }
    return NO;
    }

  • (BOOL)canBecomeFirstResponder {
    return YES;
    }

  • (void)moveLine:(UIPanGestureRecognizer *)gr {
    //If we haven’t selected a line, we don’t do anything here
    if (![self selectedLine]) {
    return;
    }

    //When the pan recognizer changes its position…
    if ([gr state] == UIGestureRecognizerStateChanged) {
    //How far has the pan moved?
    CGPoint translation = [gr translationInView:self];

      //Add the translation to the current begin and end point of the line
      CGPoint begin = [[self selectedLine] begin];
      CGPoint end = [[self selectedLine] end];
      begin.x += translation.x;
      begin.y += translation.y;
      end.x += translation.x;
      end.y += translation.y;
      
      //Set the new beginning and end points of the line
      [[self selectedLine] setBegin:begin];
      [[self selectedLine] setEnd:end];
      
      //Redraw the screen
      [self setNeedsDisplay];
      
      [gr setTranslation:CGPointZero inView:self];
    

    }
    }

  • (void)deleteLine:(id)sender {
    //Remove the selected line from the list of completeLines
    [completeLines removeObject:[self selectedLine]];

    //Redraw everything
    [self setNeedsDisplay];
    }

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

    //Draw complete lines in black
    for (Line *line in completeLines) {
    if ([line color] == nil) {
    [[UIColor blackColor] set];
    } else {
    [[line color] set];
    }
    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 linesInProcess) {
    Line *line = [linesInProcess objectForKey:v];
    CGContextMoveToPoint(context, [line begin].x, [line begin].y);
    CGContextAddLineToPoint(context, [line end].x, [line end].y);
    CGContextStrokePath(context);
    }

    //If there is a selected line, draw it
    if ([self selectedLine]) {
    [[UIColor greenColor] set];
    CGContextMoveToPoint(context, [[self selectedLine] begin].x, [[self selectedLine] begin].y);
    CGContextAddLineToPoint(context, [[self selectedLine] end].x, [[self selectedLine] end].y);
    CGContextStrokePath(context);
    }
    }

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

    //Redraw
    [self setNeedsDisplay];
    }

  • (Line *)lineAtPoint:(CGPoint)p {
    //Find a line close to p
    for (Line *l in completeLines) {
    CGPoint start = [l begin];
    CGPoint end = [l end];

      //Check a few points on the line
      for (float t = 0.0; t <= 1.0; t+=0.05) {
          float x = start.x + t * (end.x - start.x);
          float y = start.y + t * (end.y - start.y);
          
          //If the tapped point is within 20 points, let's return this line
          if (hypot(x - p.x, y- p.y) < 20.0)
              return l;
      }
    

    }

    //if nothing is close enough to the tapped point, then we didn’t select a line
    return nil;
    }

pragma mark -------------------------< TOUCH METHODS >-----------------------------

  • (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    for (UITouch *t in touches) {
    //Is this a double tap ?
    if ([t tapCount] > 1) {
    [self clearAll];
    return;
    }

      //Use the touch object (packed in an 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
      [linesInProcess setObject:newLine forKey:key];
    

    }
    }

  • (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    //Update linesInProcess with moved touches
    for (UITouch *t in touches) {
    NSValue *key = [NSValue valueWithNonretainedObject:t];

      //Find the line for this touch
      Line *line = [linesInProcess objectForKey:key];
      
      //Update the line
      CGPoint loc = [t locationInView:self];
      [line setEnd:loc];
    

    }

    //Redraw
    [self setNeedsDisplay];
    }

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

      //If this is a double tap, 'line' will be nil
      //so make sure not to add it to the array
      if (line) {
          NSLog(@"Selected Color == %@", [self selectedColor]);
          [line setColor:[self selectedColor]];
          [completeLines addObject:line];
          [linesInProcess 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];
    }

@end
[/code]

TouchViewController.h

[code]//
// TouchViewController.h
// TouchTracker
//
// Created by Christian Ki****** on 29.08.12.
// Copyright © 2012 Jörg Ki******. All rights reserved.
//

#import <Foundation/Foundation.h>
@class TouchDrawView;
@class ColorPicker;

@interface TouchViewController : UIViewController {
TouchDrawView *canvas;
ColorPicker *colorPicker;
}

  • (void)loadColorPicker;
  • (void)unloadColorPicker;
  • (void)setColorForFutureLines;

@end
[/code]

TouchViewController.m

[code]//
// TouchViewController.m
// TouchTracker
//
// Created by Christian Ki****** on 29.08.12.
// Copyright © 2012 Jörg Ki******. All rights reserved.
//

#import “TouchViewController.h”
#import “TouchDrawView.h”
#import “ColorPicker.h”

@implementation TouchViewController

  • (void)loadView {
    canvas = [[TouchDrawView alloc] initWithFrame:CGRectZero];
    [canvas setController:self];
    [self setView:canvas];
    colorPicker = nil;
    }

  • (void)loadColorPicker {
    if (colorPicker == nil) {

      colorPicker = [[ColorPicker alloc] init];
      [colorPicker setController:self];
      [[colorPicker colorPickSegCtrl] setFrame:CGRectMake(10, 500, 300, 50)];
      [canvas addSubview:[colorPicker view]];
    

    }

    [UIView animateWithDuration:1 animations:^{
    [[colorPicker colorPickSegCtrl] setFrame:CGRectMake(10, 420, 300, 50)];
    }];

}

  • (void)unloadColorPicker {
    [UIView animateWithDuration:1 animations:^{
    [[colorPicker colorPickSegCtrl] setFrame:CGRectMake(20, 500, 300, 50)];
    }];
    }

  • (void)setColorForFutureLines {
    [canvas setSelectedColor:[colorPicker selectedColor]];
    NSLog(@“Selected Color = %@”, [canvas selectedColor]);
    }

@end
[/code]

Hope that helped you out !
Joerg


#2

This was great. For practice sake, I wanted to try and do the same thing with a xib file, but I’m having a heck of a time to make it work. I’m taking a similar approach as HypnoTime, but when my view controller loads, TouchDrawView is unresponsive. Any ideas?