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 large circle displays fine, but whenever I call circle.mask = circleMask;
then the view appears empty.
Here's my code:
AriDonut.h
#import <UIKit/UIKit.h>
@interface AriDonut : UIView
-(id)initWithRadius:(float)radius;
@end
AriDonut.m
#import "AriDonut.h"
#import <QuartzCore/QuartzCore.h>
@implementation AriDonut
-(id)initWithRadius:(float)radius{
self = [super initWithFrame:CGRectMake(0, 0, radius, radius)];
if(self){
//LARGE CIRCLE
CALayer *circle = [CALayer layer];
circle.bounds = CGRectMake(0, 0, radius, radius);
circle.backgroundColor = [UIColor redColor].CGColor;
circle.cornerRadius = radius/2;
circle.position = CGPointMake(radius/2, radius/2);
//SMALL CIRLCE
CALayer *circleMask = [CALayer layer];
circleMask.bounds = CGRectMake(0, 0, 10, 10);
circleMask.cornerRadius = radius/2;
circleMask.position = circle.position;
//circle.mask = circleMask;
[self.layer addSublayer:circle];
}
return self;
}
I've tried setting the large circle's superlayer nil like this:
CALayer *theSuper = circle.superlayer;
theSuper = nil;
But it didin't make a difference.
I also tried setting Circle's masksToBounds
property to YES and NO, but it didn't make a difference.
Any thoughts?
To create a mask, you first need to select the layer you want to apply it to. Then grab a shape tools from the toolbar, which you can also cycle through quickly by pressing Q. Simply click and drag in your Composition viewer, and congrats, you've made a mask!
A clipping mask is a group of layers to which a mask is applied. The bottommost layer, or base layer, defines the visible boundaries of the entire group. For example, suppose you have a shape in the base layer, a photograph in the layer above it, and text in the topmost layer.
SwiftUI gives us the mask() modifier for masking one with another, which means you can mask an image using text or an image using an image, or more. For example, this creates a 300x300 image of stripes, then masks it using the text “SWIFT!” so that the letters act as a cut out for the image: Image("laser-show") .
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With