How to add a border just on the top side of a UIView
I consider subclassing UIView
and overriding drawRect
overkill here. Why not add an extension on UIView
and add border subviews?
@discardableResultfunc addBorders(edges: UIRectEdge, color: UIColor, inset: CGFloat = 0.0, thickness: CGFloat = 1.0) -> [UIView] { var borders = [UIView]() @discardableResult func addBorder(formats: String...) -> UIView { let border = UIView(frame: .zero) border.backgroundColor = color border.translatesAutoresizingMaskIntoConstraints = false addSubview(border) addConstraints(formats.flatMap { NSLayoutConstraint.constraints(withVisualFormat: $0, options: [], metrics: ["inset": inset, "thickness": thickness], views: ["border": border]) }) borders.append(border) return border } if edges.contains(.top) || edges.contains(.all) { addBorder(formats: "V:|-0-[border(==thickness)]", "H:|-inset-[border]-inset-|") } if edges.contains(.bottom) || edges.contains(.all) { addBorder(formats: "V:[border(==thickness)]-0-|", "H:|-inset-[border]-inset-|") } if edges.contains(.left) || edges.contains(.all) { addBorder(formats: "V:|-inset-[border]-inset-|", "H:|-0-[border(==thickness)]") } if edges.contains(.right) || edges.contains(.all) { addBorder(formats: "V:|-inset-[border]-inset-|", "H:[border(==thickness)]-0-|") } return borders} // Usage: view.addBorder(edges: [.all]) // All with default arguments view.addBorder(edges: [.top], color: .green) // Just Top, green, default thickness view.addBorder(edges: [.left, .right, .bottom], color: .red, thickness: 3) // All except Top, red, thickness 3
With this code you're not tied to your subclass too, you can apply it to anything and everything that inherits from UIView
- reusable in your project, and any others. Pass in other arguments to your methods to define other colours and widths. Many options.
Added capability for rounded corners to Adam Waite's original post, and the multiple edits
Important!: Don't forgot to add 'label.layoutIfNeeded()' right before calling 'addborder' as previously commented
Note: I've only tested this on UILabels.
extension CALayer { enum BorderSide { case top case right case bottom case left case notRight case notLeft case topAndBottom case all } enum Corner { case topLeft case topRight case bottomLeft case bottomRight } func addBorder(side: BorderSide, thickness: CGFloat, color: CGColor, maskedCorners: CACornerMask? = nil) { var topWidth = frame.size.width; var bottomWidth = topWidth var leftHeight = frame.size.height; var rightHeight = leftHeight var topXOffset: CGFloat = 0; var bottomXOffset: CGFloat = 0 var leftYOffset: CGFloat = 0; var rightYOffset: CGFloat = 0 // Draw the corners and set side offsets switch maskedCorners { case [.layerMinXMinYCorner, .layerMaxXMinYCorner]: // Top only addCorner(.topLeft, thickness: thickness, color: color) addCorner(.topRight, thickness: thickness, color: color) topWidth -= cornerRadius*2 leftHeight -= cornerRadius; rightHeight -= cornerRadius topXOffset = cornerRadius; leftYOffset = cornerRadius; rightYOffset = cornerRadius case [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]: // Bottom only addCorner(.bottomLeft, thickness: thickness, color: color) addCorner(.bottomRight, thickness: thickness, color: color) bottomWidth -= cornerRadius*2 leftHeight -= cornerRadius; rightHeight -= cornerRadius bottomXOffset = cornerRadius case [.layerMinXMinYCorner, .layerMinXMaxYCorner]: // Left only addCorner(.topLeft, thickness: thickness, color: color) addCorner(.bottomLeft, thickness: thickness, color: color) topWidth -= cornerRadius; bottomWidth -= cornerRadius leftHeight -= cornerRadius*2 leftYOffset = cornerRadius; topXOffset = cornerRadius; bottomXOffset = cornerRadius; case [.layerMaxXMinYCorner, .layerMaxXMaxYCorner]: // Right only addCorner(.topRight, thickness: thickness, color: color) addCorner(.bottomRight, thickness: thickness, color: color) topWidth -= cornerRadius; bottomWidth -= cornerRadius rightHeight -= cornerRadius*2 rightYOffset = cornerRadius case [.layerMaxXMinYCorner, .layerMaxXMaxYCorner, // All .layerMinXMaxYCorner, .layerMinXMinYCorner]: addCorner(.topLeft, thickness: thickness, color: color) addCorner(.topRight, thickness: thickness, color: color) addCorner(.bottomLeft, thickness: thickness, color: color) addCorner(.bottomRight, thickness: thickness, color: color) topWidth -= cornerRadius*2; bottomWidth -= cornerRadius*2 topXOffset = cornerRadius; bottomXOffset = cornerRadius leftHeight -= cornerRadius*2; rightHeight -= cornerRadius*2 leftYOffset = cornerRadius; rightYOffset = cornerRadius default: break } // Draw the sides switch side { case .top: addLine(x: topXOffset, y: 0, width: topWidth, height: thickness, color: color) case .right: addLine(x: frame.size.width - thickness, y: rightYOffset, width: thickness, height: rightHeight, color: color) case .bottom: addLine(x: bottomXOffset, y: frame.size.height - thickness, width: bottomWidth, height: thickness, color: color) case .left: addLine(x: 0, y: leftYOffset, width: thickness, height: leftHeight, color: color) // Multiple Sides case .notRight: addLine(x: topXOffset, y: 0, width: topWidth, height: thickness, color: color) addLine(x: 0, y: leftYOffset, width: thickness, height: leftHeight, color: color) addLine(x: bottomXOffset, y: frame.size.height - thickness, width: bottomWidth, height: thickness, color: color) case .notLeft: addLine(x: topXOffset, y: 0, width: topWidth, height: thickness, color: color) addLine(x: frame.size.width - thickness, y: rightYOffset, width: thickness, height: rightHeight, color: color) addLine(x: bottomXOffset, y: frame.size.height - thickness, width: bottomWidth, height: thickness, color: color) case .topAndBottom: addLine(x: topXOffset, y: 0, width: topWidth, height: thickness, color: color) addLine(x: bottomXOffset, y: frame.size.height - thickness, width: bottomWidth, height: thickness, color: color) case .all: addLine(x: topXOffset, y: 0, width: topWidth, height: thickness, color: color) addLine(x: frame.size.width - thickness, y: rightYOffset, width: thickness, height: rightHeight, color: color) addLine(x: bottomXOffset, y: frame.size.height - thickness, width: bottomWidth, height: thickness, color: color) addLine(x: 0, y: leftYOffset, width: thickness, height: leftHeight, color: color) } } private func addLine(x: CGFloat, y: CGFloat, width: CGFloat, height: CGFloat, color: CGColor) { let border = CALayer() border.frame = CGRect(x: x, y: y, width: width, height: height) border.backgroundColor = color addSublayer(border) } private func addCorner(_ corner: Corner, thickness: CGFloat, color: CGColor) { // Set default to top left let width = frame.size.width; let height = frame.size.height var x = cornerRadius var y = cornerRadius var startAngle: CGFloat = .pi; var endAngle: CGFloat = .pi*3/2 switch corner { case .bottomLeft: y = height - cornerRadius startAngle = .pi/2; endAngle = .pi case .bottomRight: x = width - cornerRadius y = height - cornerRadius startAngle = 0; endAngle = .pi/2 case .topRight: x = width - cornerRadius startAngle = .pi*3/2; endAngle = 0 default: break } let cornerPath = UIBezierPath(arcCenter: CGPoint(x: x, y: y), radius: cornerRadius - thickness, startAngle: startAngle, endAngle: endAngle, clockwise: true) let cornerShape = CAShapeLayer() cornerShape.path = cornerPath.cgPath cornerShape.lineWidth = thickness cornerShape.strokeColor = color cornerShape.fillColor = nil addSublayer(cornerShape) }}
The best way for me is a category on UIView, but adding views
instead of CALayers, so we can take advantage of AutoresizingMasks
to make sure borders resize along with the superview.
Objective C
- (void)addTopBorderWithColor:(UIColor *)color andWidth:(CGFloat) borderWidth { UIView *border = [UIView new]; border.backgroundColor = color; [border setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin]; border.frame = CGRectMake(0, 0, self.frame.size.width, borderWidth); [self addSubview:border];}- (void)addBottomBorderWithColor:(UIColor *)color andWidth:(CGFloat) borderWidth { UIView *border = [UIView new]; border.backgroundColor = color; [border setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin]; border.frame = CGRectMake(0, self.frame.size.height - borderWidth, self.frame.size.width, borderWidth); [self addSubview:border];}- (void)addLeftBorderWithColor:(UIColor *)color andWidth:(CGFloat) borderWidth { UIView *border = [UIView new]; border.backgroundColor = color; border.frame = CGRectMake(0, 0, borderWidth, self.frame.size.height); [border setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin]; [self addSubview:border];}- (void)addRightBorderWithColor:(UIColor *)color andWidth:(CGFloat) borderWidth { UIView *border = [UIView new]; border.backgroundColor = color; [border setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin]; border.frame = CGRectMake(self.frame.size.width - borderWidth, 0, borderWidth, self.frame.size.height); [self addSubview:border];}
Swift 5
func addTopBorder(with color: UIColor?, andWidth borderWidth: CGFloat) { let border = UIView() border.backgroundColor = color border.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin] border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: borderWidth) addSubview(border)}func addBottomBorder(with color: UIColor?, andWidth borderWidth: CGFloat) { let border = UIView() border.backgroundColor = color border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin] border.frame = CGRect(x: 0, y: frame.size.height - borderWidth, width: frame.size.width, height: borderWidth) addSubview(border)}func addLeftBorder(with color: UIColor?, andWidth borderWidth: CGFloat) { let border = UIView() border.backgroundColor = color border.frame = CGRect(x: 0, y: 0, width: borderWidth, height: frame.size.height) border.autoresizingMask = [.flexibleHeight, .flexibleRightMargin] addSubview(border)}func addRightBorder(with color: UIColor?, andWidth borderWidth: CGFloat) { let border = UIView() border.backgroundColor = color border.autoresizingMask = [.flexibleHeight, .flexibleLeftMargin] border.frame = CGRect(x: frame.size.width - borderWidth, y: 0, width: borderWidth, height: frame.size.height) addSubview(border)}