Masking a CALayer with another CALayer

后端 未结 4 804
灰色年华
灰色年华 2021-02-07 08:49

I\'m trying to make a donut shape with CALayers. One CALayer will be a large circle, the other one will be a smaller circle positioned in its center, masking it.

The la

相关标签:
4条回答
  • 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
    
    0 讨论(0)
  • 2021-02-07 09:19

    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.

    0 讨论(0)
  • 2021-02-07 09:20

    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];
    
    0 讨论(0)
  • 2021-02-07 09:27

    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:.

    0 讨论(0)
提交回复
热议问题