Masking a CALayer with another CALayer

旧街凉风 提交于 2019-12-03 05:25:36

Indeed, as @David indicates the current (iOS 5.1) CALayer masks can't be reversed, which poses a problem if you want to use them to make a transparent hole a simple circular CALayer.

What you can do to get a donut is make a circular CALayer's backgroundColor transparent, but give it a borderColor and a wide borderWidth. Here's the dunkin' code:

    CALayer *theDonut = [CALayer layer];
    theDonut.bounds = CGRectMake(0,0, radius, radius);
    theDonut.cornerRadius = radius/2;
    theDonut.backgroundColor = [UIColor clearColor].CGColor;

    theDonut.borderWidth = radius/5;
    theDonut.borderColor = [UIColor orangeColor].CGColor;

    [self.layer addSublayer:theDonut];

This is pretty easy using UIBezierPath and a CAShapeLayer as a masking layer. Code sample written as though it's in a UIView subclass.

Objective-C:

CGRect outerRect = self.bounds;
CGFloat inset = 0.2 * outerRect.size.width; // adjust as necessary for more or less meaty donuts
CGFloat innerDiameter = outerRect.size.width - 2.0 * inset;
CGRect innerRect = CGRectMake(inset, inset, innerDiameter, innerDiameter);
UIBezierPath *outerCircle = [UIBezierPath bezierPathWithRoundedRect:outerRect cornerRadius:outerRect.size.width * 0.5];
UIBezierPath *innerCircle = [UIBezierPath bezierPathWithRoundedRect:innerRect cornerRadius:innerRect.size.width * 0.5];
[outerCircle appendPath:innerCircle];
CAShapeLayer *maskLayer = [CAShapeLayer new];
maskLayer.fillRule = kCAFillRuleEvenOdd; // Going from the outside of the layer, each time a path is crossed, add one. Each time the count is odd, we are "inside" the path.
maskLayer.path = outerCircle.CGPath;
self.layer.mask = maskLayer;

Swift:

let outerRect = self.bounds
let inset: CGFloat = 0.2 * outerRect.width // adjust as necessary for more or less meaty donuts
let innerDiameter = outerRect.width - 2.0 * inset
let innerRect = CGRect(x: inset, y: inset, width: innerDiameter, height: innerDiameter)
let outerCircle = UIBezierPath(roundedRect: outerRect, cornerRadius: outerRect.width * 0.5)
let innerCircle = UIBezierPath(roundedRect: innerRect, cornerRadius: innerRect.width * 0.5)
outerCircle.appendPath(innerCircle)
let mask = CAShapeLayer()
mask.fillRule = kCAFillRuleEvenOdd
mask.path = outerCircle.CGPath
self.layer.mask = mask

It is the alpha value of the masking layers content that is used as a mask. (If you would add the mask as a sublayer instead of using it as a mask. Everything that is covered by the sublayer would be visible when used as a mask. Everything that is not covered by the sublayer would be hidden when used as a mask.)

Since your small circle is fully transparent , everything is masked away (is hidden). If you set the backgroundColor of it to any, fully opaque color (only the alpha value is used for the mask) then it will let those pixels through.

Note that this is the reverse of what you want. This will leave you with only "the hole of the donut" visible. There is no built in way to do a reverse mask Instead you would have to draw the content of the mask some other way like using a CAShapeLayer or using drawInContext:.

user511

I succeeded with a CAShapeLayer masking a CALayer. To specify the shape of the masking CAShapeLayer I used UIBezierPath.

I posted the code in my answer to this question: How to Get the reverse path of a UIBezierPath. For the donut shape uncomment the commented line.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!