UIBezierPath: How to add a border around a view wi

2019-02-08 08:27发布

问题:

I am using UIBezierPath to have my imageview have round corners but I also want to add a border to the imageview. Keep in mind the top is a uiimage and the bottom is a label.

Currently using this code produces:

let rectShape = CAShapeLayer()
rectShape.bounds = myCell2.NewFeedImageView.frame
rectShape.position = myCell2.NewFeedImageView.center
rectShape.path = UIBezierPath(roundedRect: myCell2.NewFeedImageView.bounds,
    byRoundingCorners: .TopRight | .TopLeft,
    cornerRadii: CGSize(width: 25, height: 25)).CGPath
myCell2.NewFeedImageView.layer.mask = rectShape

I want to add a green border to that but I cant use

myCell2.NewFeedImageView.layer.borderWidth = 8
myCell2.NewFeedImageView.layer.borderColor = UIColor.greenColor().CGColor

because it cuts off the top left and top right corner of the border as seen in this image:

Is there a way too add in a border with UIBezierPath along with my current code?

回答1:

You can reuse the UIBezierPath path and add a shape layer to the view. Here is an example inside a view controller.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Create a view with red background for demonstration
        let v = UIView(frame: CGRectMake(0, 0, 100, 100))
        v.center = view.center
        v.backgroundColor = UIColor.redColor()
        view.addSubview(v)

        // Add rounded corners
        let maskLayer = CAShapeLayer()
        maskLayer.frame = v.bounds
        maskLayer.path = UIBezierPath(roundedRect: v.bounds, byRoundingCorners: .TopRight | .TopLeft, cornerRadii: CGSize(width: 25, height: 25)).CGPath
        v.layer.mask = maskLayer

        // Add border
        let borderLayer = CAShapeLayer()
        borderLayer.path = maskLayer.path // Reuse the Bezier path
        borderLayer.fillColor = UIColor.clearColor().CGColor
        borderLayer.strokeColor = UIColor.greenColor().CGColor
        borderLayer.lineWidth = 5
        borderLayer.frame = v.bounds
        v.layer.addSublayer(borderLayer)   
    }

}

The end result looks like this.

Note that this only works as expected when the view's size is fixed. When the view can resize, you will need to create a custom view class and resize the layers in layoutSubviews.



回答2:

As I comment above, it is

actually not easy to do this absolutely-perfectly.

Here's the "OCD solution", this is completely drop-in code.


Here's a full, total example. This

  • correctly addresses / explains the issue that when you do this, you are drawing HALF OF THE BORDER LINE

  • is totally usable in autolayout

  • completely re-works itself when the size of the view changes or animates

  • is totally IBDesignable - you can see it in realtime in your storyboard

for 2017...

@IBDesignable
class RoundedCornersAndTrueBorder: UIView {
    @IBInspectable var cornerRadius: CGFloat = 10 {
        didSet { setup() }
    }
    @IBInspectable var borderColor: UIColor = UIColor.black {
        didSet { setup() }
    }
    @IBInspectable var trueBorderWidth: CGFloat = 2.0 {
        didSet { setup() }
    }

    override func layoutSubviews() {
        setup()
    }

    var border:CAShapeLayer? = nil

    func setup() {
        // make a path with round corners
        let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:cornerRadius)

        // note that it is >exactly< the size of the whole view

        // mask the whole view to that shape
        // note that you will ALSO be masking the border we'll draw below
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask

        // add another layer, which will be the border as such

        if (border == nil) {
            border = CAShapeLayer()
            self.layer.addSublayer(border!)
        }
        // IN SOME APPROACHES YOU would INSET THE FRAME
        // of the border-drawing layer by the width of the border
        // border.frame = bounds.insetBy(dx: borderWidth, dy: borderWidth)
        // so that when you draw the line, ALL of the WIDTH of the line
        // DOES fall within the actual mask.

        // here, we will draw the border-line LITERALLY ON THE EDGE
        // of the path. that means >HALF< THE LINE will be INSIDE
        // the path and HALF THE LINE WILL BE OUTSIDE the path
        border!.frame = bounds
        let pathUsingCorrectInsetIfAny =
          UIBezierPath(roundedRect: border!.bounds, cornerRadius:cornerRadius)

        border!.path = pathUsingCorrectInsetIfAny.cgPath
        border!.fillColor = UIColor.clear.cgColor

        // the following is not what you want:
        // it results in "half-missing corners"
        // (note however, sometimes you do use this approach):
        //border.borderColor = borderColor.cgColor
        //border.borderWidth = borderWidth

        // this approach will indeed be "inside" the path:
        border!.strokeColor = borderColor.cgColor
        border!.lineWidth = trueBorderWidth * 2.0
        // HALF THE LINE will be INSIDE the path and HALF THE LINE
        // WILL BE OUTSIDE the path. so MAKE IT >>TWICE AS THICK<<
        // as requested by the consumer class.

    }
}

Beginner help for question below ...

  1. Make a "new Swift file" called "Fattie.swift". (Note, it actually makes no difference what you call it. If you are at the stage of "don't know how to make a new file" seek basic Xcode tutorials

  2. Put all of the above code in it

  3. You've just added a class "RoundedCornersAndTrueBorder" to your project

  4. On your story board. Add an ordinary UIView to your scene. Make it actually any size/shape you prefer.

  5. In the identity inspector (seek basic tutorial if you don't know what that is, it's simple), just change the class to "RoundedCornersAndTrueBorder". (Once you start typing "Roun..." it will guess which class you mean.

  6. You're done - run the project.

Note that you have to, of course, add complete constraints to the UIView, as with absolutely anything you do in Xcode. Enjoy!



回答3:

There sure is! Every view has a layer property (which you know from giving your layer rounded corners). Another two properties on layer are borderColor and borderWidth. Just by setting those you can add a border to your view! (The border will follow the rounded corners.) Be sure to use UIColor.CGColor for borderColor as a plain UIColor won't match the type.