Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIBezierPath: How to add a border around a view with rounded corners?

Tags:

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 

current

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:

issue

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

like image 570
gooberboobbutt Avatar asked Aug 16 '15 03:08

gooberboobbutt


People also ask

How do you round the corners of a view?

You can give it round corners by changing the cornerRadius property of the view's layer . and smaller values give less rounded corners. Both clipsToBounds and masksToBounds are equivalent. It is just that the first is used with UIView and the second is used with CALayer .

How do you add corner radius to a storyboard?

Select the view that you want to round and open its Identity Inspector. In the User Defined Runtime Attributes section, add the following two entries: Key Path: layer. cornerRadius , Type: Number, Value: (whatever radius you want)

How do I make a rounded border in SwiftUI?

Any SwiftUI view can have its corners rounded using the cornerRadius() modifier.


2 Answers

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.

Simulator screenshot

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.

like image 112
hennes Avatar answered Oct 25 '22 20:10

hennes


As it says above:

It is not easy to do this perfectly.

Here's a drop-in solution.


This

  • correctly addresses the issue that 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 2019 ...

@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.              } } 

So that's it.


Beginner help for the question in the comments ...

  1. Make a "new Swift file" called "Fattie.swift". (Note, funnily enough 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 the file

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

  4. On your story board. Add an ordinary UIView to your scene. In fact, make it actually any size/shape whatsoever, anything you prefer.

  5. Look at the Identity Inspector. (If you do not know what that is, seek basic tutorials.) Simply 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 and correct constraints to the UIView, just as with absolutely anything you do in Xcode. Enjoy!

Similar solutions:

https://stackoverflow.com/a/57465440/294884 - image + rounded + shadows
https://stackoverflow.com/a/41553784/294884 - two-corner problem
https://stackoverflow.com/a/59092828/294884 - "shadows + hole" or "glowbox" problem
https://stackoverflow.com/a/57400842/294884 - the "border AND gap" problem
https://stackoverflow.com/a/57514286/294884 - basic "adding" beziers

And please also see the alternate answer below! :)

like image 26
Fattie Avatar answered Oct 25 '22 19:10

Fattie