Use Bezier Path as Clipping Mask

17.3k views Asked by At

I am wondering if it is possible to clip a view to a Bezier Path. What I mean is that I want to be able to see the view only in the region within the closed Bezier Path. The reason for this is that I have the outline of an irregular shape, and I want to fill in the shape gradually with a solid color from top to bottom. If I could make it so that a certain view is only visible within the path then I could simply create a UIView of the color I want and then change the y coordinate of its frame as I please, effectively filling in the shape. If anyone has any better ideas for how to implement this that would be greatly appreciated. For the record the filling of the shape will match the y value of the users finger, so it can't be a continuous animation. Thanks.

Update (a very long time later):

I tried your answer, Rob, and it works great except for one thing. My intention was to move the view being masked while the mask remains in the same place on screen. This is so that I can give the impression of the mask being "filled up" by the view. The problem is that with the code I have written based on your answer, when I move the view the mask moves with it. I understand that that is to be expected because all I did was add it as the mask of the view so it stands to reason that it will move if the thing it's tied to moves. I tried adding the mask as a sublayer of the superview so that it stays put, but that had very weird results. Here is my code:

self.test = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
self.test.backgroundColor = [UIColor greenColor];

[self.view addSubview:self.test];

UIBezierPath *myClippingPath = [UIBezierPath bezierPath];
[myClippingPath moveToPoint:CGPointMake(100, 100)];
[myClippingPath addCurveToPoint:CGPointMake(200, 200) controlPoint1:CGPointMake(self.screenWidth, 0) controlPoint2:CGPointMake(self.screenWidth, 50)];
[myClippingPath closePath];


CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = myClippingPath.CGPath;

self.test.layer.mask = mask;

CGRect firstFrame = self.test.frame;
firstFrame.origin.x += 100;
[UIView animateWithDuration:3 animations:^{
    self.test.frame = firstFrame;
}];

Thanks for the help already.

2

There are 2 answers

2
rob mayoff On BEST ANSWER

You can do this easily by setting your view's layer mask to a CAShapeLayer.

UIBezierPath *myClippingPath = ...

CAShapeLayer *mask = [CAShapeLayer layer];
mask.path = myClippingPath.CGPath;

myView.layer.mask = mask;

You will need to add the QuartzCore framework to your target if you haven't already.


In Swift ...

let yourCarefullyDrawnPath = UIBezierPath( .. blah blah
let maskForYourPath = CAShapeLayer()
maskForYourPath.path = carefullyRoundedBox.CGPath
layer.mask = maskForYourPath
0
Fattie On

Just an example of Rob's solution, there's a UIWebView sitting as a subview of a UIView called smoothView. smoothView uses bezierPathWithRoundedRect to make a rounded gray background (notice on right). That works fine.

But if smoothView has only normal clip-subviews, you get this:

enter image description here

If you do what Rob says, you get the rounded corners in smoothView and all subviews ...

enter image description here

Great stuff.