How to draw a CALayer border around my mask?

So, I have a CALayer that has a mask, and I want to add a border around this layer mask. For example, I set a triangle mask for a layer, and I want to have a border around this layer.

Can someone help me solve this problem?

+8
ios objective-c calayer mask
source share
6 answers

Some suggestions:

  • Use an opaque shadow instead of a border (you will have a blurry effect).
  • Create another layer, set its background color to the color that you want for your border, mask it with a mask that is slightly larger than the one you already have, to simulate the width of the border and place it in the center of your layer (it may not work with each form).
  • Perform a morphological operation on the mask image to calculate the border, for example, using the vImageDilate family of functions (more complex and may run into performance problems).
  • If you know the shape and can be described mathematically, draw it and stroke it explicitly using the Core Graphics functions.
  • Or, in the same case (mathematically known form), use CAShapeLayer to draw the border.
+4
source share

Consider this code example:

 - (void)drawRect:(CGRect)rect { CAShapeLayer *maskLayer = [CAShapeLayer layer]; //Modify to your needs CGFloat maskInsetWidth = 5.0f; CGFloat maskInsetHeight = 5.0f; CGFloat maskCornerRadius = 5.0f; CGFloat borderWidth = 2.0f; UIColor *borderColor = [UIColor blackColor]; CGRect insetRect = CGRectInset(self.bounds, maskInsetWidth, maskInsetHeight); insetRect.size.width = MAX(insetRect.size.width, 0); insetRect.size.height = MAX(insetRect.size.height, 0); CGPathRef path = [UIBezierPath bezierPathWithRoundedRect:insetRect cornerRadius:maskCornerRadius].CGPath; if (borderWidth > 0.0f && borderColor != nil) { CAShapeLayer *borderLayer = [CAShapeLayer layer]; [borderLayer setPath:path]; [borderLayer setLineWidth:borderWidth * 2.0f]; [borderLayer setStrokeColor:borderColor.CGColor]; [borderLayer setFillColor:[UIColor clearColor].CGColor]; borderLayer.frame = self.bounds; [self.layer addSublayer:borderLayer]; } [maskLayer setPath:path]; [maskLayer setFillRule:kCAFillRuleEvenOdd]; maskLayer.frame = self.bounds; [self.layer setMask:maskLayer]; } 
+4
source share

My approach in swift3.

 // Usage: self.btnGroup.roundCorner([.topRight, .bottomRight], radius: 4.0, borderColor: UIColor.red, borderWidth: 1.0) // Apply round corner and border. An extension method of UIView. public func roundCorner(_ corners: UIRectCorner, radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) { let path = UIBezierPath.init(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) let mask = CAShapeLayer() mask.path = path.cgPath self.layer.mask = mask let borderPath = UIBezierPath.init(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius)) let borderLayer = CAShapeLayer() borderLayer.path = borderPath.cgPath borderLayer.lineWidth = borderWidth borderLayer.strokeColor = borderColor.cgColor borderLayer.fillColor = UIColor.clear.cgColor borderLayer.frame = self.bounds self.layer.addSublayer(borderLayer) } 
+2
source share

In general, you cannot easily set a border around a mask. This is similar to passing a border around the transparent pixels of an image. Perhaps this can be done using image filters. In a more specific case, if you are using a simple CAShapeLayer, here is a sample code that does this:

 [CATransaction begin]; [CATransaction setDisableActions:YES]; CALayer *hostLayer = [CALayer layer]; hostLayer.backgroundColor = [NSColor blackColor].CGColor; hostLayer.speed = 0.0; hostLayer.timeOffset = 0.0; CALayer *maskedLayer = [CALayer layer]; maskedLayer.backgroundColor = [NSColor redColor].CGColor; maskedLayer.position = CGPointMake(200, 200); maskedLayer.bounds = CGRectMake(0, 0, 200, 200); CAShapeLayer *mask = [CAShapeLayer layer]; mask.fillColor = [NSColor whiteColor].CGColor; mask.position = CGPointMake(100, 100); mask.bounds = CGRectMake(0, 0, 200, 200); CGMutablePathRef path = CGPathCreateMutable(); CGPathMoveToPoint(path, NULL, 100, 100); for (int i=0; i<20; i++) { double x = arc4random_uniform(2000) / 10.0; double y = arc4random_uniform(2000) / 10.0; CGPathAddLineToPoint(path, NULL, x, y); } CGPathCloseSubpath(path); mask.path = path; CGPathRelease(path); maskedLayer.mask = mask; CAShapeLayer *maskCopy = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:mask]]; maskCopy.fillColor = NULL; maskCopy.strokeColor = [NSColor yellowColor].CGColor; maskCopy.lineWidth = 4; maskCopy.position = maskedLayer.position; // Alternately, don't set the position and add the copy as a sublayer // maskedLayer.sublayers = @[maskCopy]; hostLayer.sublayers = @[maskedLayer,maskCopy]; _contentView.layer = hostLayer; _contentView.wantsLayer = YES; [CATransaction commit]; 

It basically creates an arbitrary path and sets it as a mask. He then takes a copy of this layer to stroke the path. You may need to tweak everything to get the exact effect you're looking for.

+1
source share

Swift 2 Werner meets

enter image description here

  //setup MASK imageView.layer.mask = nil; let cornerRadius = self.imageView.bounds.height * 0.5 let maskPath = UIBezierPath(roundedRect: self.imageView.bounds, byRoundingCorners: [.TopLeft, .BottomRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)) let maskLayer = CAShapeLayer() maskLayer.frame = self.bounds; maskLayer.path = maskPath.CGPath self.imageView.layer.mask = maskLayer //setup Border for Mask let maskInsetWidth :CGFloat = 0.0 let maskInsetHeight :CGFloat = 0.0 let borderWidth: CGFloat = self.imageView.bounds.width * 0.05 let borderColor = UIColor.redColor() let insetRect = CGRectInset(imageView.bounds, maskInsetWidth, maskInsetHeight) let path = UIBezierPath(roundedRect: insetRect, byRoundingCorners: [.TopLeft, .BottomRight], cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)).CGPath let borderLayer = CAShapeLayer() borderLayer.path = path borderLayer.lineWidth = borderWidth * 2.0 borderLayer.strokeColor = borderColor.CGColor borderLayer.fillColor = UIColor.clearColor().CGColor borderLayer.frame = imageView.bounds imageView.layer.addSublayer(borderLayer) 
+1
source share

If you are a subclass of CALayer , you can create it using the mask you want, and also override layoutSubLayers to include the desired frame. This will work for all masks and should be the new accepted answer.

Could do this in several ways. Below I do this using the path this mask and assigning it to the property of the class that will be used to build a new border in layoutSubLayers . There is the potential that this method could be called several times, so I also set a boolean value to track this. (You can also set the border as a property of the class and delete / re-add each time. At the moment, I'm using the bool check.

Swift 3:

 class CustomLayer: CALayer { private var path: CGPath? private var borderSet: Bool = false init(maskLayer: CAShapeLayer) { super.init() self.path = maskLayer.path self.frame = maskLayer.frame self.bounds = maskLayer.bounds self.mask = maskLayer } override func layoutSublayers() { let newBorder = CAShapeLayer() newBorder.lineWidth = 12 newBorder.path = self.path newBorder.strokeColor = UIColor.black.cgColor newBorder.fillColor = nil if(!borderSet) { self.addSublayer(newBorder) self.borderSet = true } } required override init(layer: Any) { super.init(layer: layer) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } } 
+1
source share

All Articles