I want to draw a straight line with my finger that automatically sizes based upon how far away I am from the point of origin.
So if I touch the screen in the middle and slide my finger out a line appears to 'stretch' and pivot around the point of orgin as my finger moves on the screen. WHhen I lift my finger. The Destination Point should finalize and create a line. I can drag my finger across the screen and 'Draw' on the screen but that's not what I am wanting to do.
I thought UIBeizerPath moveToPoint would help but it just messes things up.
What am I doing wrong?
- (id)initWithFrame:(CGRect)frame
{
//default line properties
myPath=[[UIBezierPath alloc]init];
myPath.lineCapStyle=kCGLineCapRound;
myPath.miterLimit=0;
myPath.lineWidth=lineWidth;
brushPattern=[UIColor blackColor];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint curPoint = [[touches anyObject] locationInView:self];
lastPoint = curPoint;
[myPath moveToPoint:lastPoint];
[pathArray addObject:myPath];
[self setNeedsDisplay];
}
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
CGPoint curPoint = [[touches anyObject] locationInView:self];
myPath.lineWidth=lineWidth;
brushPattern=[UIColor redColor]; //red to show it hasn't been added yet.
[myPath moveToPoint:tempPoint];
[self setNeedsDisplay];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
CGPoint curPoint = [[touches anyObject] locationInView:self];
myPath.lineWidth=lineWidth;
brushPattern=[UIColor blackColor]; //finalize the line with black color
[myPath addLineToPoint:curPoint];
[self setNeedsDisplay];
}
Here's one concept. Draws a line from where you start dragging your finger until where you let go, animating it as you drag your finger around. It does this by making a CAShapeLayer
, resetting the path
as you move your finger around.
This should demonstrate the basic idea:
- (void)viewDidLoad {
[super viewDidLoad];
UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanGesture:)];
[self.view addGestureRecognizer:gesture];
}
- (CAShapeLayer *)createShapeLayer:(UIView *)view {
CAShapeLayer *shapeLayer = [[CAShapeLayer alloc] init];
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
shapeLayer.lineWidth = 3.0;
[view.layer addSublayer:shapeLayer];
return shapeLayer;
}
- (void)handlePanGesture:(UIPanGestureRecognizer *)gesture {
static CAShapeLayer *shapeLayer;
static CGPoint origin;
if (gesture.state == UIGestureRecognizerStateBegan) {
shapeLayer = [self createShapeLayer:gesture.view];
origin = [gesture locationInView:gesture.view];
} else if (gesture.state == UIGestureRecognizerStateChanged) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:origin];
CGPoint location = [gesture locationInView:gesture.view];
[path addLineToPoint:location];
shapeLayer.path = path.CGPath;
} else if (gesture.state == UIGestureRecognizerStateEnded ||
gesture.state == UIGestureRecognizerStateFailed ||
gesture.state == UIGestureRecognizerStateCancelled) {
shapeLayer = nil;
}
}
Or, in Swift 3:
override func viewDidLoad() {
super.viewDidLoad()
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
view.addGestureRecognizer(pan)
}
private func createShapeLayer(for view: UIView) -> CAShapeLayer {
let shapeLayer = CAShapeLayer()
shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.lineWidth = 3.0
view.layer.addSublayer(shapeLayer)
return shapeLayer
}
private var shapeLayer: CAShapeLayer!
private var origin: CGPoint!
func handlePan(_ gesture: UIPanGestureRecognizer) {
if gesture.state == .began {
shapeLayer = createShapeLayer(for: gesture.view!)
origin = gesture.location(in: gesture.view)
} else if gesture.state == .changed {
let path = UIBezierPath()
path.move(to: origin)
path.addLine(to: gesture.location(in: gesture.view))
shapeLayer.path = path.cgPath
} else if gesture.state == .ended || gesture.state == .failed || gesture.state == .cancelled {
shapeLayer = nil
}
}
If you don't use CAShapeLayer
, but you want to keep track of previous paths, you'll have to maintain an array for those old paths, and build a path that consists of all of the old paths, perhaps something like:
@interface CustomView ()
@property (nonatomic) CGPoint originPoint;
@property (nonatomic) CGPoint currentPoint;
@property (nonatomic) NSMutableArray *previousPaths;
@end
@implementation CustomView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self configure];
}
return self;
}
- (id)init {
return [self initWithFrame:CGRectZero];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self configure];
}
return self;
}
- (void)configure {
_previousPaths = [[NSMutableArray alloc] init];
}
- (void)drawRect:(CGRect)rect {
[[UIColor redColor] setStroke];
UIBezierPath *drawPath = [UIBezierPath bezierPath];
drawPath.lineCapStyle = kCGLineCapRound;
drawPath.miterLimit = 0;
drawPath.lineWidth = 3.0;
for (UIBezierPath *path in self.previousPaths)
[drawPath appendPath:path];
UIBezierPath *path = [self pathForCurrentLine];
if (path)
[drawPath appendPath:path];
[drawPath stroke];
}
- (UIBezierPath *)pathForCurrentLine {
if (CGPointEqualToPoint(self.originPoint, CGPointZero) && CGPointEqualToPoint(self.currentPoint, CGPointZero))
return nil;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.originPoint];
[path addLineToPoint:self.currentPoint];
return path;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.originPoint = [[touches anyObject] locationInView:self];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if ([event respondsToSelector:@selector(predictedTouchesForTouch:)]) {
touch = [[event predictedTouchesForTouch:touch] lastObject] ?: touch;
}
self.currentPoint = [touch locationInView:self];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
self.currentPoint = [[touches anyObject] locationInView:self];
[self.previousPaths addObject:[self pathForCurrentLine]];
self.originPoint = self.currentPoint = CGPointZero;
[self setNeedsDisplay];
}
@end
UIBezierPath is building a path from your instructions. Imagine a pen. When you say, "moveToPoint:" it moves the pen to that point. When you say "lineToPoint:" it puts the pen down and moves it from the current location to the new point. And so on.
To get the effect you desire, you will need to create a new path whenever the touches move, drawing a line from the original point to the current touch position.