3
votes

I'm creating an app that allows users to cut out part of an image. In order to do this, they'll create a bunch of UIBezierPaths to form the clipping path. My current setup is as follows:

  • A UIImageView displays the image they're cutting.
  • Above that UIImageView is a custom subclass of UIImageView that performs the custom drawRect: methods for showing/updating the UIBezierPaths that the user is adding.
  • When the user clicks the "Done" button, a new UIBezierPath object is created that incorporates all the individual paths created by the user by looping through the array they're stored in and calling appendPath: on itself. This new UIBezierPath then closes its path.

That's as far as I've gotten. I know UIBezierPath has an addClip method, but I can't figure out from the documentation how to use it.

In general, all examples I've seen for clipping directly use Core Graphics rather than the UIBezierPath wrapper. I realize that UIBezierPath has a CGPath property. So should I be using this at the time of clipping rather than the full UIBezierPath object?

1
how you are detecting portion which you want to clip ? By Gesture ? - Shrawan

1 Answers

0
votes

Apple say not to subclass UIImageView, according to the UIImageView class reference. Thank you to @rob mayoff for pointing this out.

However, if you're implementing your own drawRect, start with your own UIView subclass. And, it's within drawRect that you use addClip. You can do this with a UIBezierPath without converting it to a CGPath.

- (void)drawRect:(CGRect)rect
{
    // This assumes the clippingPath and image may be drawn in the current coordinate space.
    [[self clippingPath] addClip];
    [[self image] drawAtPoint:CGPointZero];
}

If you want to scale up or down to fill the bounds, you need to scale the graphics context. (You could also apply a CGAffineTransform to the clippingPath, but that is permanent, so you'd need to copy the clippingPath first.)

- (void)drawRect:(CGRect)rect
{
    // This assumes the clippingPath and image are in the same coordinate space, and scales both to fill the view bounds.
    if ([self image])
    {
        CGSize imageSize = [[self image] size];
        CGRect bounds = [self bounds];

        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextScaleCTM(context, bounds.size.width/imageSize.width, bounds.size.height/imageSize.height);

        [[self clippingPath] addClip];
        [[self image] drawAtPoint:CGPointZero];
    }
}

This will scale the image separately on each axis. If you want to preserve its aspect ratio, you'll need to work out the overall scaling, and possibly translate it so it's centered or otherwise aligned.

Finally, all of this is relatively slow if your path gets drawn a lot. You will probably find it's faster to store the image in a CALayer, and mask that with a CAShapeLayer containing the path. Do not use the following methods except for testing. You will need to separately scale the image layer and the mask to make them line up. The advantage is that you can change the mask without the underlying image being rendered.

- (void) setImage:(UIImage *)image;
{
    // This method should also store the image for later retrieval.
    // Putting an image directly into a CALayer will stretch the image to fill the layer.
    [[self layer] setContents:(id) [image CGImage]];
}

- (void) setClippingPath:(UIBezierPath *)clippingPath;
{
    // This method should also store the clippingPath for later retrieval.
    if (![[self layer] mask])
        [[self layer] setMask:[CAShapeLayer layer]];

    [(CAShapeLayer*) [[self layer] mask] setPath:[clippingPath CGPath]];
}

If you do make image clipping with layer masks work, you no longer need a drawRect method. Remove it for efficiency.