Best way to draw circle on top of UIImageView IOS
Asked Answered
Q

1

5

I have a UIImageView that shows a picture the user has just taken with the camera. On that image i need to draw a transparent movable circle thats a set size. So when the user drags their finger over the image the circle moves with it. Then if the user stops moving their finger, the circle stays in the same place.

I have been reading the apple documentation on CAShapeLayer but i'm still not sure of the best way, should i be drawing a UIView?

Any examples would be brilliant. Thanks.

Quetzal answered 10/3, 2013 at 13:15 Comment(0)
T
19

The following code creates three gestures:

  • A tap gesture drops a circle on the view (just so you can see how the CAShapeLayer is created);

  • A pan gesture moves the circle (assuming you started moving from within the circle); and

  • A pinch gesture resizes the circle.

Thus that might look like:

#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIGestureRecognizerSubclass.h>

@interface ViewController ()

@property (nonatomic, weak) CAShapeLayer *circleLayer;
@property (nonatomic) CGPoint circleCenter;
@property (nonatomic) CGFloat circleRadius;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    // create tap gesture

    UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self
                                                                          action:@selector(handleTap:)];
    [self.view addGestureRecognizer:tap];

    // create pan gesture

    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self
                                                                          action:@selector(handlePan:)];
    [self.view addGestureRecognizer:pan];

    // create pinch gesture

    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self
                                                                                action:@selector(handlePinch:)];
    [self.view addGestureRecognizer:pinch];
}

// Create a UIBezierPath which is a circle at a certain location of a certain radius.
// This also saves the circle's center and radius to class properties for future reference.

- (UIBezierPath *)makeCircleAtLocation:(CGPoint)location radius:(CGFloat)radius
{
    self.circleCenter = location;
    self.circleRadius = radius;

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path addArcWithCenter:self.circleCenter
                    radius:self.circleRadius
                startAngle:0.0
                  endAngle:M_PI * 2.0
                 clockwise:YES];

    return path;
}

// Create a CAShapeLayer for our circle on tap on the screen

- (void)handleTap:(UITapGestureRecognizer *)gesture
{
    CGPoint location = [gesture locationInView:gesture.view];

    // if there was a previous circle, get rid of it

    [self.circleLayer removeFromSuperlayer];

    // create new CAShapeLayer

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = [[self makeCircleAtLocation:location radius:50.0] CGPath];
    shapeLayer.strokeColor = [[UIColor redColor] CGColor];
    shapeLayer.fillColor = nil;
    shapeLayer.lineWidth = 3.0;

    // Add CAShapeLayer to our view

    [gesture.view.layer addSublayer:shapeLayer];

    // Save this shape layer in a class property for future reference,
    // namely so we can remove it later if we tap elsewhere on the screen.

    self.circleLayer = shapeLayer;
}

// Let's move the CAShapeLayer on a pan gesture (assuming we started
// pan inside the circle).

- (void)handlePan:(UIPanGestureRecognizer *)gesture
{
    static CGPoint oldCenter;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        // If we're starting a pan, make sure we're inside the circle.
        // So, calculate the distance between the circle's center and 
        // the gesture start location and we'll compare that to the 
        // radius of the circle.

        CGPoint location = [gesture locationInView:gesture.view];
        CGPoint translation = [gesture translationInView:gesture.view];
        location.x -= translation.x;
        location.y -= translation.y;

        CGFloat x = location.x - self.circleCenter.x;
        CGFloat y = location.y - self.circleCenter.y;
        CGFloat distance = sqrtf(x*x + y*y);

        // If we're outside the circle, cancel the gesture.
        // If we're inside it, keep track of where the circle was.

        if (distance > self.circleRadius)
            gesture.state = UIGestureRecognizerStateCancelled;
        else
            oldCenter = self.circleCenter;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        // Let's calculate the new center of the circle by adding the
        // the translationInView to the old circle center.

        CGPoint translation = [gesture translationInView:gesture.view];
        CGPoint newCenter = CGPointMake(oldCenter.x + translation.x, oldCenter.y + translation.y);

        // Update the path for our CAShapeLayer

        self.circleLayer.path = [[self makeCircleAtLocation:newCenter radius:self.circleRadius] CGPath];
    }
}

// Let's resize circle in the CAShapeLayer on a pinch gesture (assuming we have
// a circle layer).

- (void)handlePinch:(UIPinchGestureRecognizer *)gesture
{
    static CGFloat oldCircleRadius;

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        if (self.circleLayer)
            oldCircleRadius = self.circleRadius;
        else
            gesture.state = UIGestureRecognizerStateCancelled;
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        CGFloat newCircleRadius = oldCircleRadius * gesture.scale;
        self.circleLayer.path = [[self makeCircleAtLocation:self.circleCenter radius:newCircleRadius] CGPath];
    }
}

@end
Torietorii answered 10/3, 2013 at 16:39 Comment(7)
Thanks Rob, very clear and just what i was after, i would give two ticks if i could :-)Quetzal
@Torietorii the above code is great can you also help me in knowing how to crop the image portion under the circle into a separate image ? ThanksUnijugate
@Torietorii also gesture.state = UIGestureRecognizerStateCancelled; gives an error on ios7Unijugate
@vishal Re cropping the imageview, you can set the mask for the image view's layer. Re saving that cropped image to a new image, you can use drawViewHierarchyInRect and renderInContext (search for those terms in S.O. and you'll find plenty of examples). Re UIGestureRecognizerStateCancelled, did you do the necessary #import <UIKit/UIGestureRecognizerSubclass.h>? I'd suggest you do a little digging around on those topics and if you still can't figure out how to do it, post your own question, as we're going beyond the scope of this particular question.Torietorii
Here is the new question @Torietorii I couldnt do it actually I am not a seasoned iOS programmer and hence little confused , I have posted screen as well as the code i m using please see if you can provide some code in the same context #20166406Unijugate
I wonder how could the image with the circle be saved as bitmap?Exorcist
You can use the standard techniques: https://mcmap.net/q/1921259/-capture-uiview-without-using-renderincontext. Instead of using CGRect equal to bounds, though, you might adjust that for the frame that might encapsulate the circle (e.g. x = center-radius, width = radius * 2.0, etc.).Torietorii

© 2022 - 2024 — McMap. All rights reserved.