在UIView外部添加边框(而不是内部)

94

如果我在视图中使用代码为视图添加边框,例如:

self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = 2.0f;

边框被添加在视图内部,如下所示:enter image description here右侧的视图是原始视图,您可以看到,有边框的视图的黑色区域比原始视图少。但我想要的是一个在原始视图外面的边框,就像这样:enter image description here黑色区域与原始视图相等,我该如何实现?
12个回答

107
很遗憾,没有一个简单的属性可以设置边框对齐到外部。它绘制对齐到内部,因为UIView的默认绘制操作在其边界内绘制。最简单的解决方案是在应用边框时将UIView扩展边框宽度的大小。
CGFloat borderWidth = 2.0f;

self.frame = CGRectInset(self.frame, -borderWidth, -borderWidth);
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = borderWidth;

我曾在uitableviewcell的视图上使用过这个。但是在这一行代码self.frame = CGRectInset(self.frame, -borderWidth, -borderWidth); 中,如果我们不断滚动tableview,视图的宽度和高度会不断增加。因此,最终我选择将其删除。 - Neela
这个方法会不断增加视图高度,所以我删除了这段代码并使用以下代码:self.view.layer.borderColor = [[UIColor colorWithRed:209.0f/255.0f green:33.0f/255.0f blue:8.0f/255.0f alpha:1.0f] CGColor]; self.view.layer.borderWidth = 1.0f; - Syed Rizwan Jafry
5
尝试在Swift中使用此解决方案,不幸的是它并没有起作用,边框仍然绘制在UIImageView内部。 - theDC
如果这段代码在一个被多次调用的方法中,那么它会不断增加大小,因为self.frame会重复。为了防止这种情况发生,声明一个属性来存储self.frame,并在viewDidLoad中设置它。例如,_originalSize = self.frame; 其中originalSize是一个CGRect属性。然后将代码更改为self.frame = CGRectInset(_originalSize, -borderWidth, -borderWidth); - GeneCode
当在ViewDidLoad中实现时,视图会自动调整大小以适应屏幕大小(即视图调整为原始大小,因此边框变得可见)。如果在ViewDidAppear中实现,则根本不会在原始视图之外创建边框。有什么想法可以实现这样的效果,使得边框在原始视图之外创建,而视图不会调整大小? - JeffB6688
显示剩余2条评论

26

要进行Swift实现,您可以将此添加为UIView扩展。

extension UIView {

    struct Constants {
        static let ExternalBorderName = "externalBorder"
    }

    func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.whiteColor()) -> CALayer {
        let externalBorder = CALayer()
        externalBorder.frame = CGRectMake(-borderWidth, -borderWidth, frame.size.width + 2 * borderWidth, frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.CGColor
        externalBorder.borderWidth = borderWidth
        externalBorder.name = Constants.ExternalBorderName

        layer.insertSublayer(externalBorder, atIndex: 0)
        layer.masksToBounds = false

        return externalBorder
    }

    func removeExternalBorders() {
        layer.sublayers?.filter() { $0.name == Constants.ExternalBorderName }.forEach() {
            $0.removeFromSuperlayer()
        }
    }

    func removeExternalBorder(externalBorder: CALayer) {
        guard externalBorder.name == Constants.ExternalBorderName else { return }
        externalBorder.removeFromSuperlayer()
    }

}

太好了!谢谢您!Swift 4 中有一些变化,现在看起来不同了,但是Xcode会解释如何修改代码。在方法“removeExternalBorder”中必须加上“guard externalBorder.name ==”。 - DmitryKanunnikoff

26

基于上面接受的最佳答案,我有过使用不好的结果和难看的边缘的经历:

无贝塞尔路径的边框

因此,我将与您分享我的UIView Swift扩展,它使用UIBezierPath作为边框轮廓-没有丑陋的边缘(受@Fattie启发):

有贝塞尔路径的边框

//  UIView+BezierPathBorder.swift

import UIKit

extension UIView {

    fileprivate var bezierPathIdentifier:String { return "bezierPathBorderLayer" }

    fileprivate var bezierPathBorder:CAShapeLayer? {
        return (self.layer.sublayers?.filter({ (layer) -> Bool in
            return layer.name == self.bezierPathIdentifier && (layer as? CAShapeLayer) != nil
        }) as? [CAShapeLayer])?.first
    }

    func bezierPathBorder(_ color:UIColor = .white, width:CGFloat = 1) {

        var border = self.bezierPathBorder
        let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.layer.cornerRadius)
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask

        if (border == nil) {
            border = CAShapeLayer()
            border!.name = self.bezierPathIdentifier
            self.layer.addSublayer(border!)
        }

        border!.frame = self.bounds
        let pathUsingCorrectInsetIfAny =
            UIBezierPath(roundedRect: border!.bounds, cornerRadius:self.layer.cornerRadius)

        border!.path = pathUsingCorrectInsetIfAny.cgPath
        border!.fillColor = UIColor.clear.cgColor
        border!.strokeColor = color.cgColor
        border!.lineWidth = width * 2
    }

    func removeBezierPathBorder() {
        self.layer.mask = nil
        self.bezierPathBorder?.removeFromSuperlayer()
    }

}

例子:

let view = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100))
view.layer.cornerRadius = view.frame.width / 2
view.backgroundColor = .red

//add white 2 pixel border outline
view.bezierPathBorder(.white, width: 2)

//remove border outline (optional)
view.removeBezierPathBorder()

1
在我的情况下,它应该在括号内 :) - Peter Kreinz
1
思维方式不错,但你的边框仍然是“内部”,所以需要修复为轮廓 :) - Serj Rubens

25

好的,已经有一个被接受的答案,但我认为有更好的方法来完成它,你只需要添加一个比你的视图稍大的新层,并且不要将其遮罩到视图层的边界内(实际上这是默认行为)。这里是样例代码:

CALayer * externalBorder = [CALayer layer];
externalBorder.frame = CGRectMake(-1, -1, myView.frame.size.width+2, myView.frame.size.height+2);
externalBorder.borderColor = [UIColor blackColor].CGColor;
externalBorder.borderWidth = 1.0;

[myView.layer addSublayer:externalBorder];
myView.layer.masksToBounds = NO;
当然,如果您想要边框的宽度为1个单位,那么可以使用此方法。如果您需要更多宽度,可以相应地调整borderWidth和图层的框架。CALayerUIView轻,因此这比使用更大的第二个视图更好。而且您无需修改myView的框架,这很有用,特别是如果myView是一个UIImageView
注意:在模拟器上,我的结果并不完美(层有时不会在正确位置,因此有一侧会更厚),但在真实设备上恰好符合要求。
编辑:
实际上,我在N.B中提到的问题只是因为我缩小了模拟器的屏幕,正常大小完全没有问题。
希望这能帮到您。

2
myView.layer.masksToBounds = NO; 当myView具有CornerRadius时,它会出现问题。 - umakanta
.masksToBounds = no // 如果图片的缩放模式是aspectFill,这也会导致图片溢出 - iOS Blacksmith

13

实际上,没有直接的方法可以做到这一点,您可以考虑一些解决方法。

  1. Change and increase the frame and add bordercolor as you did
  2. Add a view behind the current view with the larger size so that it appears as border.Can be worked as a custom class of view
  3. If you dont need a definite border (clearcut border) then you can depend on shadow for the purpose

    [view1 setBackgroundColor:[UIColor blackColor]];
    UIColor *color = [UIColor yellowColor];
    view1.layer.shadowColor = [color CGColor];
    view1.layer.shadowRadius = 10.0f;
    view1.layer.shadowOpacity = 1;
    view1.layer.shadowOffset = CGSizeZero;
    view1.layer.masksToBounds = NO;
    

5

Swift 5

extension UIView {
    fileprivate struct Constants {
        static let externalBorderName = "externalBorder"
    }

    func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) -> CALayer {
        let externalBorder = CALayer()
        externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.cgColor
        externalBorder.borderWidth = borderWidth
        externalBorder.name = Constants.ExternalBorderName

        layer.insertSublayer(externalBorder, at: 0)
        layer.masksToBounds = false

        return externalBorder
    }

    func removeExternalBorders() {
        layer.sublayers?.filter() { $0.name == Constants.externalBorderName }.forEach() {
            $0.removeFromSuperlayer()
        }
    }

    func removeExternalBorder(externalBorder: CALayer) {
        guard externalBorder.name == Constants.externalBorderName else { return }
        externalBorder.removeFromSuperlayer()
    }
}

3

在添加边框之前,通过增加视图的边框宽度来增加视图框架的宽度和高度:

float borderWidth = 2.0f
CGRect frame = self.frame;
frame.width += borderWidth;
frame.height += borderWidth;
 self.layer.borderColor = [UIColor yellowColor].CGColor;
 self.layer.borderWidth = 2.0f;

1

我喜欢@picciano提出的解决方案。 如果您想要爆炸式的圆而不是正方形,请将addExternalBorder函数替换为:

func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
        let externalBorder = CALayer()
        externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.cgColor
        externalBorder.borderWidth = borderWidth
        externalBorder.cornerRadius = (frame.size.width + 2 * borderWidth) / 2
        externalBorder.name = Constants.ExternalBorderName
        layer.insertSublayer(externalBorder, at: 0)
        layer.masksToBounds = false

    }

1
实际上有一个非常简单的解决方案。只需将它们都设置为这样:
view.layer.borderWidth = 5

view.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5).cgColor

view.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.25).cgColor

1
我是一个有用的助手,可以为您翻译文本。
如何在Storyboard中给我的UI视图(主要-SubscriptionAd)放置边框是将其放置在另一个UI视图(背景-BackgroundAd)内。 Background UIView具有与我想要的边框颜色匹配的背景颜色,而Main UIView从每个侧面具有2个约束值。
我将链接背景视图到我的ViewController,然后通过更改背景颜色打开和关闭边框。

Image Of Nested UIView with Top View 2px constraints all around, making it smaller than larger


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