Drawing bezier curves with my finger in iOS?

Brendan picture Brendan · Jan 12, 2011 · Viewed 16.9k times · Source

Hey, I'm trying to figure out how to generate bezier curves in iOS based on user input. Are there any existing classes for this? Can someone give me a general summary of what would be required? I just need help getting started on the right foot.

Answer

ughoavgfhw picture ughoavgfhw · Jan 12, 2011

If you want to stay in objective-c, you can use UIBezierPath's addCurveToPoint:controlPoint1:controlPoint2: method. You can also use a similarly named function with CGPaths. When using bezier curves, you need 4 points: starting point, ending point, and a control point at each end to define the curve.

One way to define this is to have the user drag a finger to define the start and end points, then tap the screen at the control points. Here is an example view to handle this.

BezierView.h

enum {
    BezierStateNone = 0,
    BezierStateDefiningLine,
    BezierStateDefiningCP1,
    BezierStateDefiningCP2
};
@interface BezierView : UIView {
    CGPoint startPt, endPt, cPt1, cPt2;
    UInt8 state;
    UIBezierPath *curvePath;
  @private
    UITouch *currentTouch;
}
@property (nonatomic, retain) UIBezierPath *curvePath;
@end

BezierView.m

@interface BezierView
@dynamic curvePath;
- (UIBezierPath *)curvePath {
    return [[curvePath retain] autorelease];
}
- (void)setCurvePath:(UIBezierPath *)newPath {
    id tmp = curvePath;
    curvePath = [newPath retain];
    [tmp release];
    state = BezierStateNone;
    [self setNeedsDisplay];
}
- (void)_updateCurve {
    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:startPt];
    [path addCurveToPoint:endPt controlPoint1:cPt1 controlPoint2:cPt2];
}
- (void)_calcDefaultControls {
    if(ABS(startPt.x - endPt.x) > ABS(startPt.y - endPt.y)) {
        cPt1 = (CGPoint){(startPt.x + endPt.x) / 2, startPt.y};
        cPt2 = (CGPoint){cPt1.x, endPt.y};
    } else {
        cPt1 = (CGPoint){startPt.x, (startPt.y + endPt.y) / 2};
        cPt2 = (CGPoint){endPt.x, cPt1.y};
    }
}
- (void)drawRect:(CGRect)rect {
    UIBezierPath *path = self.curvePath;
    if(path) [path stroke];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if(currentTouch) return;
    if(state == BezierStateNone) {
        state = BezierStateDefiningLine;
        currentTouch = [touches anyObject];
        startPt = [currentTouch locationInView:self];
    } else if(state == BezierStateDefiningCP1) {
        currentTouch = [touches anyObject];
        cPt1 = [currentTouch locationInView:self];
        [self _updateCurve];
    } else if(state == BezierStateDefiningCP2) {
        currentTouch = [touches anyObject];
        cPt2 = [currentTouch locationInView:self];
        [self _updateCurve];
    }
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if(!currentTouch) return;
    if(state == BezierStateDefiningLine) {
        endPt = [currentTouch locationInView:self];
        [self _calcDefaultControls];
        [self _updateCurve];
    } else if(state == BezierStateDefiningCP1) {
        cPt1 = [currentTouch locationInView:self];
        [self _updateCurve];
    } else if(state == BezierStateDefiningCP2) {
        cPt2 = [currentTouch locationInView:self];
        [self _updateCurve];
    }
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    if(!currentTouch) return;
    if(state == BezierStateDefiningLine) {
        state = BezierStateDefiningCP1;
    } else if(state == BezierStateDefiningCP1) {
        state = BezierStateDefiningCP2;
    } else if(state == BezierStateDefiningCP2) {
        state = BezierStateNone;
    }
    currentTouch = nil;
}
- (void)touchesCanceled:(NSSet *)touches withEvent:(UIEvent *)event {
    if(state == BezierStateDefiningLine) {
        self.curvePath = nil;
        self.state = BezierStateNone;
    }
    self.currentTouch = nil;
}