2

I'm trying to build some iOS apps while learning and am having some trouble understanding the proper way to do this.

What I have currently is a view that is a subclass of UIView. It is clear and I want to use it as a drawing surface. It will sit on top of something else, like tracing paper.

The user should be able to click on one spot then another spot and a line should draw between the 2 points. I'm getting the touch data, I have the points, and I am able to draw stuff from inside of drawRect: initially.

The problem is I'm not sure how to update stuff later. When everything loads up and drawRect: is calle, it will draw a line just fine. But how do I make it draw new stuff or alter stuff that is already drawn based on what the user is doing. I know I need to call setNeedsDisplay, but not how to get the data to the view to draw stuff.

I've read a bunch of tutorials/examples and they all stop at "Override drawRect: and draw some stuff... done!". How do I pass data down in to the view to tell it "hey, redraw this stuff and add this new line".

Or am I going about this all the wrong way?

EDIT: I'll try to explain better the setup I have.

I have a VC. Inside this VC's view I have a toolbar at the bottom. The rest of the area is taken up by 2 views. One is an image view that holds a reference image. One is the custom view that is clear (tracing paper) that sits at the top. They click a button on the toolbar which turns on a gesturerecognizer. They click on the screen, and I collect the tap data, turn off the gesturerecognizer and HOPEFULLY draw a line. I've got it all working except the drawing part.

STW
  • 44,917
  • 17
  • 105
  • 161
Nathan Hess
  • 741
  • 1
  • 8
  • 18

1 Answers1

4

You are on the right track. Sounds like you need to keep track of the points.

Here's some example code.

LineDrawView.h

#import <UIKit/UIKit.h>
@interface LineDrawView : UIView 
{
    NSMutableArray *lines;
    CGPoint pointA, pointB;
    BOOL activeLine;
}
@end

LineDrawView.m

#import "LineDrawView.h"
@implementation LineDrawView
- (id)initWithFrame:(CGRect)frame 
{
    self = [super initWithFrame:frame];
    if (self) 
    {
        self.backgroundColor = [UIColor blackColor];
        lines = [[NSMutableArray alloc] init];
    }
    return self;
}
- (void)dealloc 
{
    [lines release];
    [super dealloc];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesBegan:touches withEvent:event];
    CGPoint point = [[touches anyObject] locationInView:self];
    if ([lines count] == 0) pointA = point;
    else pointB = point;
    activeLine = YES;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];
    pointB = [[touches anyObject] locationInView:self];
    [self setNeedsDisplay];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    pointB = [[touches anyObject] locationInView:self];
    [lines addObject:[NSArray arrayWithObjects:[NSValue valueWithCGPoint:pointA], [NSValue valueWithCGPoint:pointB], nil]];
    pointA = pointB;
    pointB = CGPointZero;
    activeLine = NO;
    [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect
{
    CGContextRef c = UIGraphicsGetCurrentContext();
    CGContextSetLineWidth(c, 2);
    CGContextSetLineCap(c, kCGLineCapRound);
    CGContextSetLineJoin(c, kCGLineJoinRound);
    CGContextSetStrokeColorWithColor(c, [UIColor grayColor].CGColor);
    for (NSArray *line in lines)
    {
        CGPoint points[2] = { [[line objectAtIndex:0] CGPointValue], [[line objectAtIndex:1] CGPointValue] };
        CGContextAddLines(c, points, 2);
    }
    CGContextStrokePath(c);
    if (activeLine)
    {
        CGContextSetStrokeColorWithColor(c, [UIColor whiteColor].CGColor);
        CGPoint points2[2] = { pointA, pointB };
        CGContextAddLines(c, points2, 2);
        CGContextStrokePath(c);
    }
}
@end
tidwall
  • 6,881
  • 2
  • 36
  • 47
  • I read up on touchesBegan etc.. and they seem to be not what I want. I edited my question to try to elaborate on what I'm doing. I'm using a VC and that VC turns on/off a gesture recognizer depnding on what the user is doing. I'm collecting the points just fine and storing them fine. I just don't know how to pass that data down to the view to do the drawing. Correct me if I'm wrong, but your solution seems to rely solely on the custom UIView. I want it to work where the custom UIView is dumb. And only draws lines based on data I pass to it from the VC. Am I wrong with that approach? – Nathan Hess May 31 '11 at 22:24
  • 1
    No you are not wrong and your approach should work fine. You should be able to ignore the `touches` methods and fill the `line` array directly from a different class. It does not matter how the points and lines get filled as long as you call `setNeedsDisplay` right after updating the array. – tidwall May 31 '11 at 23:11
  • Oh.. I'm an idiot. I just need to set some of the custom views variables from inside the VC and then call setNeedsDisplay don't I? – Nathan Hess May 31 '11 at 23:36
  • 1
    Another option that may enhance performance is if the VC maintains a `CGPath` rather than an array of points. Then the VC can hand the custom view the path prior to calling `setNeedsDisplay`. The `drawRect:` could then simply call `CGContextDrawPath` rather than loop through every point each time. – tidwall Jun 01 '11 at 00:30