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
Without further ado, here's exactly how you do this.
The key facts are
So, setup in the usual way
import UIKit
@IBDesignable class GreenCirclePerson: UIView {
@IBInspectable var borderColor: UIColor = UIColor.black { didSet { setup() } }
@IBInspectable var trueBorderThickness: CGFloat = 2.0 { didSet { setup() } }
@IBInspectable var trueGapThickness: CGFloat = 2.0 { didSet { setup() } }
@IBInspectable var picture: UIImage? = nil { didSet { setup() } }
override func layoutSubviews() { setup() }
var imageLayer: CALayer? = nil
var border: CAShapeLayer? = nil
func setup() {
if (imageLayer == nil) {
imageLayer = CALayer()
self.layer.addSublayer(imageLayer!)
}
if (border == nil) {
border = CAShapeLayer()
self.layer.addSublayer(border!)
}
Now carefully make the layer for the circularly-cropped image:
// the ultimate size of our custom control:
let box = self.bounds.aspectFit()
let totalInsetOnAnyOneSide = trueBorderThickness + trueGapThickness
let boxInWhichImageSits = box.inset(by:
UIEdgeInsets(top: totalInsetOnAnyOneSide, left: totalInsetOnAnyOneSide,
bottom: totalInsetOnAnyOneSide, right: totalInsetOnAnyOneSide))
// just a note. that version of inset#by is much clearer than the
// confusing dx/dy variant, so best to use that one
imageLayer!.frame = boxInWhichImageSits
imageLayer!.contents = picture?.cgImage
imageLayer?.contentsGravity = .resizeAspectFill
let halfImageSize = boxInWhichImageSits.width / 2.0
let maskPath = UIBezierPath(roundedRect: imageLayer!.bounds,
cornerRadius:halfImageSize)
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
imageLayer!.mask = maskLayer
Next as a completely separate layer, draw the border as you wish:
// now create the border
border!.frame = bounds
// To draw the border, you must inset it by half the width of the border,
// otherwise you'll be drawing only half the border. (Indeed, as an additional
// subtle problem you are clipping rather than rendering the outside edge.)
let halfWidth = trueBorderThickness / 2.0
let borderCenterlineBox = box.inset(by:
UIEdgeInsets(top: halfWidth, left: halfWidth,
bottom: halfWidth, right: halfWidth))
let halfBorderBoxSize = borderCenterlineBox.width / 2.0
let borderPath = UIBezierPath(roundedRect: borderCenterlineBox,
cornerRadius:halfBorderBoxSize)
border!.path = borderPath.cgPath
border!.fillColor = UIColor.clear.cgColor
border!.strokeColor = borderColor.cgColor
border!.lineWidth = trueBorderThickness
}
}
Everything works perfectly as in iOS standard controls:
Everything which is invisible is invisible; you can see-through the overall custom control to any material behind, there are no "half thickness" problems or missing image material, you can set the custom control background color in the usual way, etc etc. The inspector controls all work properly. (Phew!)
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