167

My question is on the title.

I don't know how to add a border in a specific side, top or bottom, any side... layer.border draws the border for the whole view...

Shamas S
  • 7,507
  • 10
  • 46
  • 58
manonthemoon
  • 2,611
  • 8
  • 26
  • 40
  • possible duplicate of [UIView bottom border?](http://stackoverflow.com/questions/7666863/uiview-bottom-border) – Cœur Aug 03 '15 at 10:30

35 Answers35

217

I consider subclassing UIView and overriding drawRect overkill here. Why not add an extension on UIView and add border subviews?

@discardableResult
func 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.

ScottyBlades
  • 12,189
  • 5
  • 77
  • 85
Adam Waite
  • 19,175
  • 22
  • 126
  • 148
  • 4
    The only downside here is that it can't resize. – Peter DeWeese Jul 30 '14 at 20:46
  • 2
    Could use a UIView and add auto layout constraints. Same principle. – Adam Waite Jul 31 '14 at 12:02
  • 1
    @PeterDeWeese This isn't a downside either - if you want control over the size of the border, all you need to do is something like: - (void)addUpperBorderWithSize:(CGFloat)size And then replace the constant with parameter in the function. Same goes for other parameters such as colors. – o.shnn Aug 07 '14 at 08:12
  • As pointed out... this is fragile; it won't work with view changes, device rotation, Split View etc. See Paul's answer below on how to handle these issues. – Womble Jul 07 '15 at 05:07
  • I agree with @PeterDeWeese, I think that it would be even better to use autoLayout in the answer. I think that we should almost always use auto layout, people reading answers should get this best practice. – manonthemoon Oct 09 '15 at 07:15
  • 1
    @AdamWaite , this auto layout variant looks very good. Thank you ! – manonthemoon Oct 19 '15 at 15:04
  • Can you provide objective-C version of it? – Bista Dec 29 '15 at 07:49
  • Objective C solution: http://stackoverflow.com/questions/34507734/add-specific-border-to-uiview-with-auto-layout/34593348#34593348 – Dan H Jan 05 '16 at 16:41
  • The solution stopped working when migrating to Swift 3, there are a few syntactical issues with addSubview and addConstraints. – EmbCoder Jul 18 '16 at 20:34
  • 1
    Just tried the 19/10/15 on UITextView. Doesn't work. Since @TomSawyer said it didn't work with UICollectionView, perhaps this doesn't work with UIScrollView and its subclasses – John Indra Oct 26 '16 at 03:02
  • 2
    Thanks for sharing, I make a version of Objective-C with auto layout constraint [here](https://gist.github.com/xingheng/a0338cc677e84eb5585681a9ab4fcb8e). – Itachi Jun 23 '17 at 04:30
  • It does not work for UICollectionViewController and UITableViewController, because self.view is the collectionView/tableView in those cases and the constraints don't work with the internal scrollView. – Davide Apr 19 '18 at 06:56
  • Won't this just add unlimited amounts of borders if you call it again and again? E.g when being reused for a UITableViewCell where it needs different borders for different cells. If you need top-border in one cell, and only bot border in another cell, it will show both top and bot if it was reused.. Scroll a lot and it will have multiple tops and multiple bots..? Never removed from superview. I think I prefer subclassing. – Sti May 22 '18 at 21:05
  • @AdamWaite How can i add color for border to specific side? – Kudos Jun 28 '21 at 17:54
106

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)
    }
}
Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
Dan
  • 4,197
  • 6
  • 34
  • 52
  • 4
    This code is not working on Swift 3 for some reason, after changing the CGRectMake signature and the CGColor per Swift 3. – EmbCoder Jul 18 '16 at 20:19
  • There is a good article here https://medium.com/swift-programming/swift-cgrect-cgsize-cgpoint-5f4196da9cf8#.c6ylcnnhv that talks about what you need to do for Swift 3. – Micah Montoya Oct 21 '16 at 20:41
  • 2
    Any luck implementing this in swift 3? –  Feb 07 '17 at 15:35
  • 10
    To make it work, you need to add `view.layoutIfNeeded()` right before calling `view.layer.addBorder(...)`. Then it works in Swift 3 – Adam Studenic Mar 19 '17 at 18:46
  • it's working fine. add the line in this method `override func viewDidLayoutSubviews()` – Antony Raphel Jun 12 '17 at 05:10
  • @Dan, breaks are unnecessary within switch statements when using swift (unless you want a nop). – gangelo Sep 12 '17 at 14:32
  • @gangelo a default clause is required in order to make the switch statement exhaustive. The break is there because the default case needs some code to execute in order to compile. – jowie Aug 21 '18 at 11:58
  • 1
    You need to half the thickness when setting the radius on the UIBezierPath for it to work correctly for >= 1 thickness. – Martin_G May 20 '21 at 06:42
  • @Martin_G yes right, but not for arcs but for lines to. Line any way will be drawn at specified path with half thickness at both sides, so it should be corrected only at half. – Cy-4AH Mar 22 '22 at 11:07
  • That's awesome thanks so much! wdyt about adding this small doc comment to the solution so that it's easier for readers not to omit the the `layoutIfNeeded` call like: `/// When used to draw border for \`UIView\` ensure to call \`layoutIfNeeded()\` on view containing layer just before adding the border` ``` – dziobaczy Apr 13 '22 at 14:15
86

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)
}
denis_lor
  • 6,212
  • 4
  • 31
  • 55
Pauls
  • 2,596
  • 19
  • 19
  • 9
    This is by far one of the best solutions to this problem. Most of the other solutions offer do NOT honour view changes (and thus device rotation or Split Views). This one does. – Womble Jul 07 '15 at 05:05
  • 3
    how can this be adapted to support rounded corners? – ntaj Nov 08 '18 at 22:10
  • damn! This works like magic even though when I print (self.frame.width) in init of my custom view I get 0. But the top border is proper! – Parth Jan 18 '21 at 14:29
38

Swift 3.0

Swift 4.1

extension CALayer {

  func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) {

    let border = CALayer()

    switch edge {
    case UIRectEdge.top:
        border.frame = CGRect(x: 0, y: 0, width: frame.width, height: thickness)

    case UIRectEdge.bottom:
        border.frame = CGRect(x:0, y: frame.height - thickness, width: frame.width, height:thickness)

    case UIRectEdge.left:
        border.frame = CGRect(x:0, y:0, width: thickness, height: frame.height)

    case UIRectEdge.right:
        border.frame = CGRect(x: frame.width - thickness, y: 0, width: thickness, height: frame.height)

    default: do {}
    }

    border.backgroundColor = color.cgColor

    addSublayer(border)
 }
}
ScottyBlades
  • 12,189
  • 5
  • 77
  • 85
AleyRobotics
  • 970
  • 1
  • 10
  • 17
24

Subclass UIView and implement drawRect: in your subclass, e.g.:

Objective-c

- (void)drawRect:(CGRect)rect {
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMinY(rect));
    CGContextAddLineToPoint(context, CGRectGetMaxX(rect), CGRectGetMinY(rect));
    CGContextSetStrokeColorWithColor(context, [[UIColor redColor] CGColor] );
    CGContextSetLineWidth(context, 2.0);
    CGContextStrokePath(context);
}

Swift 4

override func draw(_ rect: CGRect) {

    let cgContext = UIGraphicsGetCurrentContext()
    cgContext?.move(to: CGPoint(x: rect.minX, y: rect.minY))
    cgContext?.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))
    cgContext?.setStrokeColor(UIColor.red.cgColor)
    cgContext?.setLineWidth(2.0)
    cgContext?.strokePath()
}

This draws a 2 pixel red line as a top border. All of the other variations you mention are left as a trivial exercise for the reader.

Quartz 2D Programming Guide is recommended.

Fonix
  • 11,447
  • 3
  • 45
  • 74
FluffulousChimp
  • 9,157
  • 3
  • 35
  • 42
  • Is it the only one way for that ??? So if I need to make a view with a top border, and other view with bottom border, and other view ... I mean always a specific view with one border, or two, I will have to create a subclass for each specific cases ??? I don't know if it's really nice to create class just for adding borders... – manonthemoon Jun 28 '13 at 18:36
  • 1
    No. Think about it. Your `UIView` subclass could have a property that determines what borders `drawRect:` draws. This property could be defined with `NS_OPTIONS` so that it defines a bitmask like the `UIViewAutoresizing` bitmask. If - for whatever reason - you really object that strongly to subclassing `UIView` then just add a small 1-2 pixel high (or wide) subview and give it whatever dimensions you want to simulate a border. – FluffulousChimp Jun 29 '13 at 10:26
  • Hey guys, I made a Swift subclass inspired by this code, hope you enjoy it: https://gist.github.com/asiviero/4f52ab7dea7d9252a64c – asiviero Jul 02 '15 at 16:14
19

Code for the selected answer, in case anyone wants it.

NOTE: This does not work with autolayout (aka, rotating device to landscape, etc).

First define a thickness:

NSInteger borderThickness = 1;

Then just copy use any or all of these to set the border you want to set.

Top Border

UIView *topBorder = [UIView new];
topBorder.backgroundColor = [UIColor lightGrayColor];
topBorder.frame = CGRectMake(0, 0, myView.frame.size.width, borderThickness);
[myView addSubview:topBorder];

Bottom Border

UIView *bottomBorder = [UIView new];
bottomBorder.backgroundColor = [UIColor lightGrayColor];
bottomBorder.frame = CGRectMake(0, myView.frame.size.height - borderThickness, myView.frame.size.width, borderThickness);
[myView addSubview:bottomBorder];

Left Border

UIView *leftBorder = [UIView new];
leftBorder.backgroundColor = [UIColor lightGrayColor];
leftBorder.frame = CGRectMake(0, 0, borderThickness, myView.frame.size.height);
[myView addSubview:leftBorder];

Right Border

UIView *rightBorder = [UIView new];
rightBorder.backgroundColor = [UIColor lightGrayColor];
rightBorder.frame = CGRectMake(myView.frame.size.width - borderThickness, 0, borderThickness, myView.frame.size.height);
[myView addSubview:rightBorder];
Travis M.
  • 10,930
  • 1
  • 56
  • 72
  • Way straightforward solution... with a UIView overhead comparing to bare CALayer – Soberman Sep 08 '15 at 09:18
  • after reading all the other solutions I was thinking I'd just add a tiny view as well. All that work for a border! This solution just requires a few pins to work in auto layout as well. Way easier to get your head around. – noobsmcgoobs Oct 30 '15 at 06:48
  • Thanks, I added a note above so that coders will only use if their app does not rotate. – Travis M. May 13 '16 at 17:13
17

Old question, but the autolayout-solution with runtime border adjustments still missing.

borders(for: [.left, .bottom], width: 2, color: .red)

The following UIView extension will add the border only on the given edges. If you change the edges at runtime, the borders will adjust accordingly.

extension UIView {
    func borders(for edges:[UIRectEdge], width:CGFloat = 1, color: UIColor = .black) {

        if edges.contains(.all) {
            layer.borderWidth = width
            layer.borderColor = color.cgColor
        } else {
            let allSpecificBorders:[UIRectEdge] = [.top, .bottom, .left, .right]

            for edge in allSpecificBorders {
                if let v = viewWithTag(Int(edge.rawValue)) {
                    v.removeFromSuperview()
                }

                if edges.contains(edge) {
                    let v = UIView()
                    v.tag = Int(edge.rawValue)
                    v.backgroundColor = color
                    v.translatesAutoresizingMaskIntoConstraints = false
                    addSubview(v)

                    var horizontalVisualFormat = "H:"
                    var verticalVisualFormat = "V:"

                    switch edge {
                    case UIRectEdge.bottom:
                        horizontalVisualFormat += "|-(0)-[v]-(0)-|"
                        verticalVisualFormat += "[v(\(width))]-(0)-|"
                    case UIRectEdge.top:
                        horizontalVisualFormat += "|-(0)-[v]-(0)-|"
                        verticalVisualFormat += "|-(0)-[v(\(width))]"
                    case UIRectEdge.left:
                        horizontalVisualFormat += "|-(0)-[v(\(width))]"
                        verticalVisualFormat += "|-(0)-[v]-(0)-|"
                    case UIRectEdge.right:
                        horizontalVisualFormat += "[v(\(width))]-(0)-|"
                        verticalVisualFormat += "|-(0)-[v]-(0)-|"
                    default:
                        break
                    }

                    self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: horizontalVisualFormat, options: .directionLeadingToTrailing, metrics: nil, views: ["v": v]))
                    self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: verticalVisualFormat, options: .directionLeadingToTrailing, metrics: nil, views: ["v": v]))
                }
            }
        }
    }
}
seeppp
  • 300
  • 3
  • 10
14

Swift version:

var myView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
myView.backgroundColor = UIColor.yellowColor() 

var border = CALayer()
border.backgroundColor = UIColor.lightGrayColor()
border.frame = CGRect(x: 0, y: 0, width: myView.frame.width, height: 0.5)

myView.layer.addSublayer(border)

Edit: For updated versions check my repo here: https://github.com/goktugyil/EZSwiftExtensions/blob/master/Sources/UIViewExtensions.swift

Look at the addBorder parts

Esqarrouth
  • 38,543
  • 21
  • 161
  • 168
9

Swift 4.2 and AutoLayout

I went through the offered solutions. Many are based in frames This is a simple extension that works with AutoLayout - use View instead of Layer to make sure we can use AutoLayout - Single subview with 4 constraints

Use as follows:

self.addBorder(.bottom, color: .lightGray, thickness: 0.5)


extension UIView {
    func addBorder(_ edge: UIRectEdge, color: UIColor, thickness: CGFloat) {
        let subview = UIView()
        subview.translatesAutoresizingMaskIntoConstraints = false
        subview.backgroundColor = color
        self.addSubview(subview)
        switch edge {
        case .top, .bottom:
            subview.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
            subview.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
            subview.heightAnchor.constraint(equalToConstant: thickness).isActive = true
            if edge == .top {
                subview.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
            } else {
                subview.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
            }
        case .left, .right:
            subview.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
            subview.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
            subview.widthAnchor.constraint(equalToConstant: thickness).isActive = true
            if edge == .left {
                subview.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 0).isActive = true
            } else {
                subview.rightAnchor.constraint(equalTo: self.rightAnchor, constant: 0).isActive = true
            }
        default:
            break
        }
    }
}
eharo2
  • 2,553
  • 1
  • 29
  • 39
5

Swift 5

Compiling some of the previous answers and adding extra methods for convenience

import UIKit

extension UIView {
    enum ViewSide {
        case top
        case left
        case bottom
        case right
    }

    func addBorders(to sides: [ViewSide], in color: UIColor, width: CGFloat) {
        sides.forEach { addBorder(to: $0, in: color, width: width) }
    }

    func addBorder(to side: ViewSide, in color: UIColor, width: CGFloat) {
        switch side {
        case .top:
            addTopBorder(in: color, width: width)
        case .left:
            addLeftBorder(in: color, width: width)
        case .bottom:
            addBottomBorder(in: color, width: width)
        case .right:
            addRightBorder(in: color, width: width)
        }
    }

    func addTopBorder(in color: UIColor?, width borderWidth: CGFloat) {
        let border = UIView()
        border.backgroundColor = color
        border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: borderWidth)
        border.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
        addSubview(border)
    }

    func addBottomBorder(in color: UIColor?, width borderWidth: CGFloat) {
        let border = UIView()
        border.backgroundColor = color
        border.frame = CGRect(x: 0, y: frame.size.height - borderWidth, width: frame.size.width, height: borderWidth)
        border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
        addSubview(border)
    }

    func addLeftBorder(in color: UIColor?, width 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(in color: UIColor?, width borderWidth: CGFloat) {
        let border = UIView()
        border.backgroundColor = color
        border.frame = CGRect(x: frame.size.width - borderWidth, y: 0, width: borderWidth, height: frame.size.height)
        border.autoresizingMask = [.flexibleHeight, .flexibleLeftMargin]
        addSubview(border)
    }
}

You can either:

  1. Set each border individually through a specific method or with the general method, selecting your case
  2. Set an array of borders

Example of usage:

// Adding top border with 2 px in red
let myView = UIView()
myView.addBorder(to: .top, in: .red, width: 2)

(Maybe you could remove some of the ambiguity that comes from two possible available interfaces and just make the helper methods private... I wouldn't remove them from there and move every case to the main method because, the way I see it, it may clutter its body).

4

I took both Adam Waite's and Pauls answers and combined them. I also added the possibility to pipe the selected edges together, so you need to call only one function like so:

[self.view addBordersToEdge:(UIRectEdgeLeft|UIRectEdgeRight)
                  withColor:[UIColor grayColor]
                   andWidth:1.0];

or so:

[self.view addBordersToEdge:(UIRectEdgeAll)
                  withColor:[UIColor grayColor]
                   andWidth:1.0];

What you need to implement is a category on UIView as suggested in other answers with the following implementation:

- (void)addBordersToEdge:(UIRectEdge)edge withColor:(UIColor *)color andWidth:(CGFloat) borderWidth {
    if (edge & UIRectEdgeTop) {
        UIView *border = [UIView new];
        border.backgroundColor = color;
        [border setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin];
        border.frame = CGRectMake(0, 0, self.frame.size.width, borderWidth);
        [self addSubview:border];
    }

    if (edge & UIRectEdgeLeft) {
        UIView *border = [UIView new];
        border.backgroundColor = color;
        border.frame = CGRectMake(0, 0, borderWidth, self.frame.size.height);
        [border setAutoresizingMask:UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleRightMargin];
        [self addSubview:border];
    }

    if (edge & UIRectEdgeBottom) {
        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];
    }

    if (edge & UIRectEdgeRight) {
        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];
    }
}
Maco03
  • 86
  • 4
4

//MARK:- Add LeftBorder For View

(void)prefix_addLeftBorder:(UIView *) viewName
{
    CALayer *leftBorder = [CALayer layer];
    leftBorder.backgroundColor = [UIColor colorWithRed:221/255.0f green:221/255.0f blue:221/255.0f alpha:1.0f].CGColor;
    leftBorder.frame = CGRectMake(0,0,1.0,viewName.frame.size.height);
    [viewName.layer addSublayer:leftBorder];
}

//MARK:- Add RightBorder For View

(void)prefix_addRightBorder:(UIView *) viewName
{
    CALayer *rightBorder = [CALayer layer];
    rightBorder.backgroundColor = [UIColor colorWithRed:221/255.0f green:221/255.0f blue:221/255.0f alpha:1.0f].CGColor;
    rightBorder.frame = CGRectMake(viewName.frame.size.width - 1.0,0,1.0,viewName.frame.size.height);
    [viewName.layer addSublayer:rightBorder];
}

//MARK:- Add Bottom Border For View

(void)prefix_addbottomBorder:(UIView *) viewName
{
    CALayer *bottomBorder = [CALayer layer];
    bottomBorder.backgroundColor = [UIColor colorWithRed:221/255.0f green:221/255.0f blue:221/255.0f alpha:1.0f].CGColor;
    bottomBorder.frame = CGRectMake(0,viewName.frame.size.height - 1.0,viewName.frame.size.width,1.0);
    [viewName.layer addSublayer:bottomBorder];
}
Gahan
  • 4,075
  • 4
  • 24
  • 44
4

I made some changes to Dan's answer so that I could add borders to multiple edges with one command:

infoView.addBorder(toEdges: [.left, .bottom, .right], color: borderColor, thickness: 1)

Here's the full code:

extension UIView {
    func addBorder(toEdges edges: UIRectEdge, color: UIColor, thickness: CGFloat) {

        func addBorder(toEdge edges: UIRectEdge, color: UIColor, thickness: CGFloat) {
            let border = CALayer()
            border.backgroundColor = color.cgColor

            switch edges {
            case .top:
                border.frame = CGRect(x: 0, y: 0, width: frame.width, height: thickness)
            case .bottom:
                border.frame = CGRect(x: 0, y: frame.height - thickness, width: frame.width, height: thickness)
            case .left:
                border.frame = CGRect(x: 0, y: 0, width: thickness, height: frame.height)
            case .right:
                border.frame = CGRect(x: frame.width - thickness, y: 0, width: thickness, height: frame.height)
            default:
                break
            }

            layer.addSublayer(border)
        }

        if edges.contains(.top) || edges.contains(.all) {
            addBorder(toEdge: .top, color: color, thickness: thickness)
        }

        if edges.contains(.bottom) || edges.contains(.all) {
            addBorder(toEdge: .bottom, color: color, thickness: thickness)
        }

        if edges.contains(.left) || edges.contains(.all) {
            addBorder(toEdge: .left, color: color, thickness: thickness)
        }

        if edges.contains(.right) || edges.contains(.all) {
            addBorder(toEdge: .right, color: color, thickness: thickness)
        }
    }
}
NSExceptional
  • 1,368
  • 15
  • 12
3

Building off of NSBum's answer, I took a similar approach and created this simple UIView subclass so that it works in Interface Builder and works with constraints: github link
By using CGContextFillRect instead of CGContextStrokePath, I was able to predictably keep the lines completely solid and within the bounds of the view.

Here's my blog post about it: http://natrosoft.com/?p=55

-- Basically just drop in a UIView in Interface Builder and change its class type to NAUIViewWithBorders.
-- Then in your VC's viewDidLoad do something like:

/* For a top border only ———————————————- */
self.myBorderView.borderColorTop = [UIColor redColor];
self.myBorderView..borderWidthsAll = 1.0f;

/* For borders with different colors and widths ————————— */
self.myBorderView.borderWidths = UIEdgeInsetsMake(2.0, 4.0, 6.0, 8.0);
self.myBorderView.borderColorTop = [UIColor blueColor];
self.myBorderView.borderColorRight = [UIColor redColor];
self.myBorderView.borderColorBottom = [UIColor greenColor];
self.myBorderView.borderColorLeft = [UIColor darkGrayColor];

Here's a direct link to the .m file so you can see the implementation. There is a demo project as well. Hope this helps someone :)

Community
  • 1
  • 1
n8tr
  • 5,018
  • 2
  • 32
  • 33
3

In case someone will ever need Xamarin version:

public static class UIUtils
{
    public static void AddBorder(this CALayer cALayer, UIRectEdge edge, UIColor color, float thickness)
    {

        var border = new CALayer();
        switch (edge) 
        {
            case UIRectEdge.Top:
                border.Frame = new CGRect(0, 0, cALayer.Frame.Width, height: thickness);
                break;
            case UIRectEdge.Bottom:
                border.Frame = new CGRect(0, cALayer.Frame.Height - thickness, width: cALayer.Frame.Width, height: thickness);
                break;
            case UIRectEdge.Left:
                border.Frame = new CGRect(0, 0, width: thickness, height: cALayer.Frame.Height);
                break;
            case UIRectEdge.Right:
                border.Frame = new CGRect(cALayer.Frame.Width - thickness, y: 0, width: thickness, height: cALayer.Frame.Height);
                break;
            default: break;
        }
        border.BackgroundColor = color.CGColor;
        cALayer.AddSublayer(border);
    }
}
Oleg Novosad
  • 2,261
  • 1
  • 27
  • 28
2

My answer to a similar question: https://stackoverflow.com/a/27141956/435766 I personally prefer going down the category road on that one, since I want to be able to use it on any subclass of UIView.

Community
  • 1
  • 1
P. Unto
  • 236
  • 1
  • 11
2

For Xamarin in C# I just create the border inline when adding the sub layer

  View.Layer.AddSublayer(new CALayer()
    {
        BackgroundColor = UIColor.Black.CGColor,
        Frame = new CGRect(0, 0, View.Frame.Width, 0.5f)
    });

You can arrange this (as suggested by others) for bottom, left and right borders.

Craig Champion
  • 442
  • 9
  • 16
2
extension UIView {

    func addBorder(edge: UIRectEdge, color: UIColor, borderWidth: CGFloat) {

        let seperator = UIView()
        self.addSubview(seperator)
        seperator.translatesAutoresizingMaskIntoConstraints = false

        seperator.backgroundColor = color

        if edge == .top || edge == .bottom
        {
            seperator.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
            seperator.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
            seperator.heightAnchor.constraint(equalToConstant: borderWidth).isActive = true

            if edge == .top
            {
                seperator.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
            }
            else
            {
                seperator.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
            }
        }
        else if edge == .left || edge == .right
        {
            seperator.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true
            seperator.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true
            seperator.widthAnchor.constraint(equalToConstant: borderWidth).isActive = true

            if edge == .left
            {
                seperator.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true
            }
            else
            {
                seperator.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true
            }
        }
    }

}
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Ali Seymen
  • 771
  • 6
  • 9
2

Swift 3 version

extension UIView {
    enum ViewSide {
        case Top, Bottom, Left, Right
    }

    func addBorder(toSide side: ViewSide, withColor color: UIColor, andThickness thickness: CGFloat) {

        let border = CALayer()
        border.backgroundColor = color.cgColor

        switch side {
        case .Top:
            border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: thickness)
        case .Bottom:
            border.frame = CGRect(x: 0, y: frame.size.height - thickness, width: frame.size.width, height: thickness)
        case .Left:
            border.frame = CGRect(x: 0, y: 0, width: thickness, height: frame.size.height)
        case .Right:
            border.frame = CGRect(x: frame.size.width - thickness, y: 0, width: thickness, height: frame.size.height)
        }

        layer.addSublayer(border)
    }
}

In order to set corresponding border you should override viewDidLayoutSubviews() method:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    yourView.addBorder(toSide: UIView.ViewSide.Top, withColor: UIColor.lightGray, andThickness: 1)
karjaubayev
  • 571
  • 5
  • 10
1

Here's a simple solution. Add a label onto your UIView, clear the text on the label and set the label background color to be your border color. Set the origin (x,y) of your label to be the origin (x,y) of your view. and set the width of the label to be the width of your UIView, set the height to be 1 or 2 (for your border height at the top of your UIView). And that should do the trick.

Rishil Patel
  • 1,977
  • 3
  • 14
  • 30
If Pollavith
  • 228
  • 2
  • 5
  • 28
    No point in having a label then, might as well just be another UIView – maor10 Aug 21 '13 at 15:46
  • 1
    This doesn't really answer the question of adding only the top border to a uiview. This is a quick fix which probably wouldn't work for more complex problems. – sam_smith Jun 20 '14 at 05:02
  • 1
    see this: http://stackoverflow.com/questions/7022656/calayer-add-a-border-only-at-one-side – Roland Kákonyi Aug 20 '14 at 09:38
1

If I'm building from within the storyboard, I prefer add an UIView behind my useful UIView... If I want to create a border on the top of my UIView, I just increase the height of the background UIView by my border width.. The same can be done for any other side :)

Rishil Patel
  • 1,977
  • 3
  • 14
  • 30
Adrián Rivero
  • 161
  • 1
  • 9
1

Just posting here to help someone looking for adding borders. I have made a few changes in the accepted answer here swift label only border left. Changed width in case UIRectEdge.Top from CGRectGetHeight(self.frame) to CGRectGetWidth(self.frame) and in case UIRectEdge.Bottom from UIScreen.mainScreen().bounds.width to CGRectGetWidth(self.frame) to get borders correctly. Using Swift 2.

Finally the extension is :

extension CALayer {

func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) {

    let border = CALayer();

    switch edge {
    case UIRectEdge.Top:
        border.frame = CGRectMake(0, 0, CGRectGetWidth(self.frame), thickness); 
        break
    case UIRectEdge.Bottom:
        border.frame = CGRectMake(0, CGRectGetHeight(self.frame) - thickness, CGRectGetWidth(self.frame), thickness)
        break
    case UIRectEdge.Left:
        border.frame = CGRectMake(0, 0, thickness, CGRectGetHeight(self.frame))
        break
    case UIRectEdge.Right:
        border.frame = CGRectMake(CGRectGetWidth(self.frame) - thickness, 0, thickness, CGRectGetHeight(self.frame))
        break
    default:
        break
    }

    border.backgroundColor = color.CGColor;

    self.addSublayer(border)
}

}
Community
  • 1
  • 1
Kavisha
  • 155
  • 1
  • 10
1

Convert DanShev answer to Swift 3

extension CALayer {

func addBorder(edge: UIRectEdge, color: UIColor, thickness: CGFloat) {

    let border = CALayer()

    switch edge {
    case .top:
        border.frame = CGRect(x: 0, y: 0, width: self.frame.width, height: thickness)
        break
    case .bottom:
        border.frame = CGRect(x: 0, y: self.frame.height - thickness, width: self.frame.width, height: thickness)
        break
    case .left:
        border.frame = CGRect(x: 0, y: 0, width: thickness, height: self.frame.height)
        break
    case .right:
        border.frame = CGRect(x: self.frame.width - thickness, y: 0, width: thickness, height: self.frame.height)
        break
    default:
        break
    }

    border.backgroundColor = color.cgColor;

    self.addSublayer(border)
}
}
Community
  • 1
  • 1
ADS
  • 23
  • 5
1

Here's a Swift 4 version of Pauls's answer

func addTopBorder(color: UIColor, thickness: CGFloat) {
    let border = UIView()
    border.backgroundColor = color
    border.autoresizingMask = [.flexibleWidth, .flexibleBottomMargin]
    border.frame = CGRect(x: 0, y: 0, width: frame.size.width, height: thickness)
    addSubview(border)
}

func addBottomBorder(color: UIColor, thickness: CGFloat) {
    let border = UIView()
    border.backgroundColor = color
    border.autoresizingMask = [.flexibleWidth, .flexibleTopMargin]
    border.frame = CGRect(x: 0, y: frame.size.height - thickness, width: frame.size.width, height: thickness)
    addSubview(border)
}

func addLeftBorder(color: UIColor, thickness: CGFloat) {
    let border = UIView()
    border.backgroundColor = color
    border.autoresizingMask = [.flexibleHeight, .flexibleRightMargin]
    border.frame = CGRect(x: 0, y: 0, width: thickness, height: frame.size.height)
    addSubview(border)
}

func addRightBorder(color: UIColor, thickness: CGFloat) {
    let border = UIView()
    border.backgroundColor = color
    border.autoresizingMask = [.flexibleHeight, .flexibleLeftMargin]
    border.frame = CGRect(x: frame.size.width - thickness, y: 0, width: thickness, height: frame.size.height)
    addSubview(border)
}
NSExceptional
  • 1,368
  • 15
  • 12
1

Inspired by @Addison I've rewritten the extension without the use of any third party framework, as he used SnapKit and CocoaLumberjack.

As in @Addisons approach I'm also removing previously added borders, so this implementation should play nice with reusable views as table cells and collection cells.

fileprivate class BorderView: UIView {} // dummy class to help us differentiate among border views and other views
                                        // to enabling us to remove existing borders and place new ones

extension UIView {

    func setBorders(toEdges edges: [UIRectEdge], withColor color: UIColor, inset: CGFloat = 0, thickness: CGFloat) {
        // Remove existing edges
        for view in subviews {
            if view is BorderView {
                view.removeFromSuperview()
            }
        }
        // Add new edges
        if edges.contains(.all) {
            addSidedBorder(toEdge: [.left,.right, .top, .bottom], withColor: color, inset: inset, thickness: thickness)
        }
        if edges.contains(.left) {
            addSidedBorder(toEdge: [.left], withColor: color, inset: inset, thickness: thickness)
        }
        if edges.contains(.right) {
            addSidedBorder(toEdge: [.right], withColor: color, inset: inset, thickness: thickness)
        }
        if edges.contains(.top) {
            addSidedBorder(toEdge: [.top], withColor: color, inset: inset, thickness: thickness)
        }
        if edges.contains(.bottom) {
            addSidedBorder(toEdge: [.bottom], withColor: color, inset: inset, thickness: thickness)
        }
    }

    private func addSidedBorder(toEdge edges: [RectangularEdges], withColor color: UIColor, inset: CGFloat = 0, thickness: CGFloat) {
        for edge in edges {
            let border = BorderView(frame: .zero)
            border.backgroundColor = color
            addSubview(border)
            border.translatesAutoresizingMaskIntoConstraints = false
            switch edge {
            case .left:
                NSLayoutConstraint.activate([
                border.leftAnchor.constraint(equalTo: self.leftAnchor, constant: inset),
                    border.topAnchor.constraint(equalTo: self.topAnchor, constant: inset),
                    border.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -inset),
                    NSLayoutConstraint(item: border, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: thickness) ])
            case .right:
                NSLayoutConstraint.activate([
                    border.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -inset),
                    border.topAnchor.constraint(equalTo: self.topAnchor, constant: inset),
                    border.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -inset),
                    NSLayoutConstraint(item: border, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: thickness) ])
            case .top:
                NSLayoutConstraint.activate([
                    border.leftAnchor.constraint(equalTo: self.leftAnchor, constant: inset),
                    border.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -inset),
                    border.topAnchor.constraint(equalTo: self.topAnchor, constant: inset),
                    NSLayoutConstraint(item: border, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: thickness) ])
            case .bottom:
                NSLayoutConstraint.activate([
                    border.leftAnchor.constraint(equalTo: self.leftAnchor, constant: inset),
                    border.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -inset),
                    border.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -inset),
                    NSLayoutConstraint(item: border, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: thickness) ])
            }
        }
    }

    private enum RectangularEdges {
        case left
        case right
        case top
        case bottom
    }
}
Andrej
  • 7,266
  • 4
  • 38
  • 57
0

Personally I like the sub-classing of the view + drawRect, but here's just another way of going about it (and it kind of works along the same lines as the accepted answer by @If Pollavith):

Your new border layer can be set up to have whatever dimensions you like. So, like @If Pollavith's answer, you create a layer to be as tall as you want it to be, and as wide as the view you want to have bordered. Use the layer's frame definition to place it where you want it, and then add it as a sub-layer to your view.

For reference, my own requirement was to put a border on the LEFT-HAND side of the view (please don't cut and paste this code and dis' me just 'cos it doesn't put a border at the top of the view -- modifying the code below is simple enough):

    CALayer *leftBorder = [CALayer layer];
leftBorder.borderColor = [UIColor colorWithRed:0.0 green:91.0/255.0 blue:141.0/255.0 alpha:1.0].CGColor;
leftBorder.borderWidth = 1;
leftBorder.frame = CGRectMake(0, 0, 1.0, CGRectGetHeight(self.myTargetView.frame));
[self.myTargetView.layer addSublayer:leftBorder];

I guess the only moderate benefit over this and making a small UIView or UILabel is that the CALayer is supposedly 'lighter-weight', and there's a lot of interesting views (as in opinions) about over-riding drawRect versus using CALayers (like here: iOS: Using UIView's 'drawRect:' vs. its layer's delagate 'drawLayer:inContext:').

Animal451

I like the colour blue.

Community
  • 1
  • 1
Animal451
  • 1,159
  • 10
  • 10
0

In addition to n8tr can add that there is an availability to set them from storyboard either:
- add two properties like borderColor and borderWidth in .h file;
- then you could add keyPaths right in storyboard, see link to screenshot

Community
  • 1
  • 1
0

You can also check this collection of UIKit and Foundation categories: https://github.com/leszek-s/LSCategories

It allows adding border on one side of UIView with single line of code:

[self.someView lsAddBorderOnEdge:UIRectEdgeTop color:[UIColor blueColor] width:2];

and it properly handles view rotation while most of answers posted here do not handle it well.

Leszek Szary
  • 9,763
  • 4
  • 55
  • 62
0

Note: most solutions here are not adaptive and will not resize. The solutions that will resize will have a massive impact on your startup time since they use a lot of CPU.

You can use this solution beneath. It works on UIBezierPaths which are lighter than layers, causing quick startup times. It is easy to use, see instructions beneath.

class ResizeBorderView: UIView {
    var color = UIColor.white
    var lineWidth: CGFloat = 1
    var edges = [UIRectEdge](){
        didSet {
            setNeedsDisplay()
        }
    }
    override func draw(_ rect: CGRect) {
        if edges.contains(.top) || edges.contains(.all){
            let path = UIBezierPath()
            path.lineWidth = lineWidth
            color.setStroke()
            UIColor.blue.setFill()
            path.move(to: CGPoint(x: 0, y: 0 + lineWidth / 2))
            path.addLine(to: CGPoint(x: self.bounds.width, y: 0 + lineWidth / 2))
            path.stroke()
        }
        if edges.contains(.bottom) || edges.contains(.all){
            let path = UIBezierPath()
            path.lineWidth = lineWidth
            color.setStroke()
            UIColor.blue.setFill()
            path.move(to: CGPoint(x: 0, y: self.bounds.height - lineWidth / 2))
            path.addLine(to: CGPoint(x: self.bounds.width, y: self.bounds.height - lineWidth / 2))
            path.stroke()
        }
        if edges.contains(.left) || edges.contains(.all){
            let path = UIBezierPath()
            path.lineWidth = lineWidth
            color.setStroke()
            UIColor.blue.setFill()
            path.move(to: CGPoint(x: 0 + lineWidth / 2, y: 0))
            path.addLine(to: CGPoint(x: 0 + lineWidth / 2, y: self.bounds.height))
            path.stroke()
        }
        if edges.contains(.right) || edges.contains(.all){
            let path = UIBezierPath()
            path.lineWidth = lineWidth
            color.setStroke()
            UIColor.blue.setFill()
            path.move(to: CGPoint(x: self.bounds.width - lineWidth / 2, y: 0))
            path.addLine(to: CGPoint(x: self.bounds.width - lineWidth / 2, y: self.bounds.height))
            path.stroke()
        }
    }
}
  1. Set your UIView's class to ResizeBorderView
  2. Set the color and line width by using yourview.color and yourview.lineWidth in your viewDidAppear method
  3. Set the edges, example: yourview.edges = [.right, .left] ([.all]) for all
  4. Enjoy quick start and resizing borders
J. Doe
  • 12,159
  • 9
  • 60
  • 114
0

For setting Top Border and Bottom Border for a UIView in Swift.

let topBorder = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 1))
topBorder.backgroundColor = UIColor.black
myView.addSubview(topBorder)

let bottomBorder = UIView(frame: CGRect(x: 0, y: myView.frame.size.height - 1, width: 10, height: 1))
bottomBorder.backgroundColor = UIColor.black
myView.addSubview(bottomBorder)
Sudhi 9135
  • 745
  • 8
  • 17
0

In Swift 4 and 3

let borderThickness = 2
let topBorder = UIView()
topBorder.backgroundColor = UIColor.red
topBorder.frame = CGRect(x: 0, y: 0, width: 
                  Int(yourViewFromOutlet.frame.size.width), height: 
                  borderThickness)
yourViewFromOutlet.addSubview(topBorder)
Naishta
  • 11,885
  • 4
  • 72
  • 54
0

I came up with a solution using Swift 4.1 that uses AutoLayout and UIViews so that it will resize with the view.

It is based off @AdamWaite 's answer but solves the issue for table view cells where the cells are reused. If the previous border is not removed before adding a new one, then the reused cell will contain multiple borders.

This is solved by creating a dummy BorderView class and searching the subview's for the borders and removing them.

This code uses SnapKit for constraints and CocoaLumberjack for debugging. If you are not using these, then it should be fairly straightforward to convert

class BorderView: UIView {
}

extension UIView {
    private func addSidedBorder(toEdge edge: UIRectEdge, withColor color: UIColor, inset: CGFloat, thickness: CGFloat) {
        let border = BorderView(frame: .zero)
        border.backgroundColor = color
        addSubview(border)

        border.snp.makeConstraints { make in
            switch edge {
            case .top:
                make.top.equalToSuperview()
                make.height.equalTo(thickness)
                make.left.right.equalToSuperview().inset(inset)

            case .left:
                make.left.equalToSuperview()
                make.width.equalTo(thickness)
                make.top.bottom.equalToSuperview().inset(inset)

            case .bottom:
                make.bottom.equalToSuperview()
                make.height.equalTo(thickness)
                make.left.right.equalToSuperview().inset(inset)

            case .right:
                make.right.equalToSuperview()
                make.width.equalTo(thickness)
                make.top.bottom.equalToSuperview().inset(inset)

            default:
                DDLogWarn("Invalid sided border given in ExtendedUIView, border not added correctly")
            }
        }
    }

    func addBorder(toEdge edge: UIRectEdge, withColor color: UIColor = .black, inset: CGFloat = 0.0, thickness: CGFloat = 1.0) {
        // Remove existing borders from view and readd them
        for view in subviews {
            if view is BorderView {
                view.removeFromSuperview()
            }
        }

        if edge.contains(.all) {
            addSidedBorder(toEdge: .top, withColor: color, inset: inset, thickness: thickness)
            addSidedBorder(toEdge: .left, withColor: color, inset: inset, thickness: thickness)
            addSidedBorder(toEdge: .bottom, withColor: color, inset: inset, thickness: thickness)
            addSidedBorder(toEdge: .right, withColor: color, inset: inset, thickness: thickness)
        } else {
            if edge.contains(.top) {
                addSidedBorder(toEdge: .top, withColor: color, inset: inset, thickness: thickness)
            }

            if edge.contains(.left) {
                addSidedBorder(toEdge: .left, withColor: color, inset: inset, thickness: thickness)
            }

            if edge.contains(.bottom) {
                addSidedBorder(toEdge: .bottom, withColor: color, inset: inset, thickness: thickness)
            }

            if edge.contains(.right) {
                addSidedBorder(toEdge: .right, withColor: color, inset: inset, thickness: thickness)
            }
        }
    }
}

Add this code and then within a class that subclasses UIView, call:

view.addBorder(toEdge: [.left, .right], withColor: .red, inset: 0.0, thickness: 1.0)
Rishil Patel
  • 1,977
  • 3
  • 14
  • 30
Addison
  • 509
  • 7
  • 9
  • This answer is great and properly solves the issue for redrawing borders. However the `BorderView` dummy class should be marked as `fileprivate` as it's only accessed within this extension. – Andrej Oct 25 '18 at 13:15
  • What's the use of BorderView? I guess you could've directly used UIView as separater. – Shivam Pokhriyal Feb 20 '19 at 15:17
  • Not entirely necessary but I use it to remove any existing border's in the view before adding new borders. Prevents multiple borders being added to the view. – Addison Feb 20 '19 at 18:18
0

For me works

extension UIView {

    func addBorders(edges: UIRectEdge = .all, color: UIColor = .black, width: CGFloat = 1.0) {

        func createBorder() -> UIView {
            let borderView = UIView(frame: CGRect.zero)
            borderView.translatesAutoresizingMaskIntoConstraints = false
            borderView.backgroundColor = color
            return borderView
        }

        if (edges.contains(.all) || edges.contains(.top)) {
            let topBorder = createBorder()
            self.addSubview(topBorder)
            NSLayoutConstraint.activate([
                topBorder.topAnchor.constraint(equalTo: self.topAnchor),
                topBorder.leadingAnchor.constraint(equalTo: self.leadingAnchor),
                topBorder.trailingAnchor.constraint(equalTo: self.trailingAnchor),
                topBorder.heightAnchor.constraint(equalToConstant: width)
                ])
        }
        if (edges.contains(.all) || edges.contains(.left)) {
            let leftBorder = createBorder()
            self.addSubview(leftBorder)
            NSLayoutConstraint.activate([
                leftBorder.topAnchor.constraint(equalTo: self.topAnchor),
                leftBorder.bottomAnchor.constraint(equalTo: self.bottomAnchor),
                leftBorder.leadingAnchor.constraint(equalTo: self.leadingAnchor),
                leftBorder.widthAnchor.constraint(equalToConstant: width)
                ])
        }
        if (edges.contains(.all) || edges.contains(.right)) {
            let rightBorder = createBorder()
            self.addSubview(rightBorder)
            NSLayoutConstraint.activate([
                rightBorder.topAnchor.constraint(equalTo: self.topAnchor),
                rightBorder.bottomAnchor.constraint(equalTo: self.bottomAnchor),
                rightBorder.trailingAnchor.constraint(equalTo: self.trailingAnchor),
                rightBorder.widthAnchor.constraint(equalToConstant: width)
                ])
        }
        if (edges.contains(.all) || edges.contains(.bottom)) {
            let bottomBorder = createBorder()
            self.addSubview(bottomBorder)
            NSLayoutConstraint.activate([
                bottomBorder.bottomAnchor.constraint(equalTo: self.bottomAnchor),
                bottomBorder.leadingAnchor.constraint(equalTo: self.leadingAnchor),
                bottomBorder.trailingAnchor.constraint(equalTo: self.trailingAnchor),
                bottomBorder.heightAnchor.constraint(equalToConstant: width)
                ])
        }
    }
}
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Vadym
  • 543
  • 5
  • 16
0

This is an alternative approach to most of the other answers. It is a quick and easy way to solve problems like this, and can be a pretty handy / minimalistic approach when prototyping designs programmatically (e.g. to avoid Interface Builder).

The technique is easy to remember, quick to write, easy to reverse engineer, and it's also useful for solving graphics problems besides just adding borders.


class YetAnotherViewController : UIViewController {

    class DualBorderedView : UIView {
        let lineWidth = 0.5
        override func draw(_ rect: CGRect) {
            UIColor.secondaryLabel.setStroke()
            let h = frame.size.height
            let w = frame.size.width
            let path = UIBezierPath()
            path.lineWidth = lineWidth
            path.move(to:    CGPoint(x: 0, y: lineWidth / 2))
            path.addLine(to: CGPoint(x: w, y: lineWidth / 2))
            path.move(to:    CGPoint(x: 0, y: h - lineWidth / 2))
            path.addLine(to: CGPoint(x: w, y: h - lineWidth / 2))
            path.stroke()
        }
    }
}

One benefit to this approach is that borders resize with the view via Autolayout. Thus you can constrain edges of the subclassed to other views and the lines stretch between them, and adapt as the views move (with screen or Autolayout changes).

In Swift you can nest your subclass, for example, in a UIViewController, making it private class that doesn't interfere with anything outside the one VC in your app.

With some imagination you can easily solve a lot of of simple graphics / connector problems with this technique without a lot of extra code and design.

So in use you can just construct this subclass wherever you would construct UIView(), for example:


let dualBorderedView = DualBorderedView()
dualBorderedView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(dualBorderedView)

NSLayoutConstraint.activate([
   dualBorderedView.heightAnchor.constraint(equalToConstant: 50.0),
   dualBorderedView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
   dualBorderedView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
   dualBorderedView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])

Note: In case it isn't obvious, linewidth / 2 references are to prevent half of width of the stroke from being clipped off the edge of the view, since the center of the line is drawn at 0 and width or height dimensions is right at the edge of the view, thus the line thickness straddles the inside and outside of the view, so this offsets the line center by half the thickness of the line to allow the full thickness of the line to be seen at the view border.


A lot of the other answers show a real flexible approach and provide a 'micro API' allowing one to tweak various things such as border width by drawing rectangle. Being able to tweak everything via constructor or function parameters can be nice to have, but sometimes it's overkill, more code to read. Sometimes it's just nice to have a specific 'widget'.

clearlight
  • 12,255
  • 11
  • 57
  • 75
-7

Use below code in viewDidLoad

- (void)viewDidLoad
{
    [super viewDidLoad];


    [self.view.layer setBorderWidth: 1.0];
    [self.view.layer setCornerRadius:8.0f];
    [self.view.layer setMasksToBounds:YES];
    [self.view.layer setBorderColor:[[UIColor colorWithRed:251.0f/255.0f green:185.0f/255.0f blue:23.0f/255.0f alpha:1.0f]];`

}

this code set red color border to your view

Jitendra
  • 5,055
  • 2
  • 22
  • 42
Neha
  • 27
  • 1
  • 5
  • 4
    The good answer was already given.Thanks. Your code, is for creating complete border. In my question, I asked about add border just in one side. – manonthemoon Jul 02 '13 at 15:54