如何在CALayer的遮罩周围绘制边框?

19

所以,我有一个 CALayer,它有一个遮罩层,我想在这个图层的遮罩周围添加边框。例如,我已经将三角形遮罩设置为该图层,并且我想在该图层周围有一个边框。

有人可以帮助我解决这个问题吗?


你最终解决了这个问题吗? - user754905
实际上不是。我需要这个来显示图层的选择。最终我改变了图层的透明度。 - Fahri Azimov
@FahriAzimov 请审查并考虑我的答案。谢谢! - Michael Ramos
6个回答

17

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)
}

1
@Cœur,我根据你的评论更新了Swift 4的代码。 - iluvatar_GR

6

我的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)
}

5

考虑以下示例代码:

- (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];
}

1
首先,感谢Werner。如果您正在以编程方式绘制掩码,则这是一个很好的解决方案。如果掩码是从图像设置的,有什么解决方案吗? - Fahri Azimov
@FahriAzimov 你可以尝试从图片中提取路径:使用 PocketSVG 或者 OpenCV - Cœur

4

一些建议:

  • 使用不透明的阴影代替边框(可以产生模糊效果)。
  • 创建另一层,将其背景颜色设置为所需的边框颜色,并使用比已有掩码稍大的掩码来模拟边框宽度,然后将其居中放置在您的图层后面(可能无法用于每种形状)。
  • 对掩码图像执行形态学操作以计算边框,例如使用vImageDilate函数族(更复杂,可能会遇到性能问题)。
  • 如果您知道形状并且可以用数学方式描述,请使用Core Graphics函数显式地绘制和描边。
  • 或者,在相同情况下(已知数学形状),使用CAShapeLayer来绘制边框。

1
如果您子类化CALayer,则可以使用所需的掩码实例化它,并覆盖layoutSubLayers以包括所需的边框。这适用于所有掩码,并应成为新的可接受答案。
有几种方法可以实现此目的。以下是使用给定掩码的path并将其分配给类属性以用于在layoutSubLayers中构造新边框的方法。存在多次调用此方法的可能性,因此我还设置了一个布尔值来跟踪此操作。(也可以将边框分配为类属性,并在每次删除/重新添加时进行。现在我使用bool检查。 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() {

        if(!borderSet) {
            self.borderSet = true
            let newBorder = CAShapeLayer()

            newBorder.lineWidth = 12
            newBorder.path = self.path
            newBorder.strokeColor = UIColor.black.cgColor
            newBorder.fillColor = nil

            self.addSublayer(newBorder)
        }

    }

    required override init(layer: Any) {
        super.init(layer: layer)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

1
在一般情况下,你不能轻易地在遮罩周围设置边框。这就像要求在图像的透明像素周围放置边框一样。也许可以使用图像过滤器来实现。在某些更具体的情况下,如果你正在使用普通的CAShapeLayer,那么这里是一段代码示例:
[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];

它基本上创建了一个任意路径并将其设置为蒙版。然后它复制这个图层来描边这条路径。你可能需要微调一些东西才能得到你想要的确切效果。

不必使用archivedDataWithRootObject+unarchiveObjectWithData来创建一个精确的副本,而是可以更快地创建一个新的图层([CAShapeLayer new]),并再次手动设置其boundspath - Cœur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接