Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to draw CALayer border around its mask?

So, I have a CALayer, which has a mask & I want to add border around this layer's mask. For example, I have set triangle mask to the layer and I want to have border around that layer.

Can anyone please help me to solve this problem?

like image 245
Fahri Azimov Avatar asked Apr 05 '13 11:04

Fahri Azimov


4 Answers

Swift 4

enter image description here

class CustomView: UIView {
override func draw(_ rect: CGRect) {
    super.draw(rect)
    self.backgroundColor = UIColor.black

    //setup path for mask and border
    let halfHeight = self.bounds.height * 0.5
    let maskPath = UIBezierPath(roundedRect: self.bounds,
                                byRoundingCorners: [.topLeft, .bottomRight],
                                cornerRadii: CGSize(width: halfHeight,
                                                    height: halfHeight))

    //setup MASK
    self.layer.mask = nil;
    let maskLayer = CAShapeLayer()
    maskLayer.frame = self.bounds;
    maskLayer.path = maskPath.cgPath
    self.layer.mask = maskLayer

    //setup Border for Mask
    let borderLayer = CAShapeLayer()
    borderLayer.path = maskPath.cgPath
    borderLayer.lineWidth = 25
    borderLayer.strokeColor = UIColor.red.cgColor
    borderLayer.fillColor = UIColor.clear.cgColor
    borderLayer.frame = self.bounds
    self.layer.addSublayer(borderLayer)
}
like image 135
iluvatar_GR Avatar answered Oct 17 '22 00:10

iluvatar_GR


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)
}
like image 30
AechoLiu Avatar answered Oct 17 '22 00:10

AechoLiu


Consider this example code:

- (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];
}
like image 5
Werner Altewischer Avatar answered Oct 17 '22 01:10

Werner Altewischer


Some suggestions:

  • Use an opaque shadow instead of a border (you will have a blurred effect).
  • Create another layer, set its background color with the color you want for your border, mask it with a mask slightly bigger than the one you already have to simulate the border width, and put it centered behind your layer (may not work with every shape).
  • Do a morphological operation on your mask image to calculate the border, for instance with the vImageDilate family of functions (more complicated, and may run into performance problems).
  • If you know the shape and it can be described mathematically, draw it and stroke it explicitly with Core Graphics functions.
  • Or, in the same case (shape known mathematically), use a CAShapeLayer to draw the border.
like image 4
Guillaume Avatar answered Oct 17 '22 00:10

Guillaume