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

前端 未结 4 1378
故里飘歌
故里飘歌 2020-12-01 14:31

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

4条回答
  •  执笔经年
    2020-12-01 14:47

    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! :)

提交回复
热议问题