65

I can add a border to a CALayer in this way:

[webView.layer setBorderColor: [[UIColor colorWithRed:0.6 green:0.7 blue:0.2 alpha:1] CGColor]];
[webView.layer setBorderWidth: 2.75];   

But is it possible to add a border only at one side? I only need a border at the bottom. Or can I reach this with other properties, e.g. frame, bounds, mask, ...?

enter image description here

Thanks for your help!


@Control-V

        UIWebView *webView = [[UIWebView alloc] init];
        CALayer *webViewLayer = webView.layer;

        // now you can do a lot of stuff like borders:
        [webViewLayer setBorderColor: [[UIColor greenColor] CGColor]];
        [webViewLayer setBorderWidth: 2.75];    

Have a look at the CALayer documentation: https://developer.apple.com/documentation/quartzcore/calayer

And have a look here: http://iosdevelopertips.com/cocoa/add-rounded-corners-and-border-to-uiwebview.html

Cœur
  • 37,241
  • 25
  • 195
  • 267
Manni
  • 11,108
  • 15
  • 49
  • 67
  • I am still wondering how did you find layer properties for UIWebView. i search hard to find out in apple official docs. – Praveen-K Aug 11 '11 at 08:10
  • @Control-V: I edit my question for you – Manni Aug 11 '11 at 08:28
  • 9
    You're hijacking the webview's layer?? **THAT IS A TERRIBLE IDEA. PLEASE DON'T DO THAT**. Except in extremely rare circumstances, you should always consider `UIWebView` to be a non-introspectable and fully opaque object. Don't even try to go mucking around with things like its layer (since its layer is NOT a `CALayer`, but rather a `CATiledLayer`, for example), because `UIWebView` can be VERY particular about how things are supposed to be configured. – Dave DeLong Aug 11 '11 at 15:19

7 Answers7

131

I made a right border using this:

leftScrollView.clipsToBounds = YES;

CALayer *rightBorder = [CALayer layer];
rightBorder.borderColor = [UIColor darkGrayColor].CGColor;
rightBorder.borderWidth = 1;
rightBorder.frame = CGRectMake(-1, -1, CGRectGetWidth(leftScrollView.frame), CGRectGetHeight(leftScrollView.frame)+2);

[leftScrollView.layer addSublayer:rightBorder];
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Kazzar
  • 1,432
  • 1
  • 11
  • 9
  • 2
    Thank you for your reply, I will have a look on your code soon! – Manni Dec 22 '11 at 10:14
  • @Kazzar, On your first line did you really mean [CALayer layer] or did you mean [webView layer]? – Victor Engel Jan 04 '13 at 02:30
  • 2
    If you try using this on a view that doesn't clip subviews, then it won't work. You can toggle this in the "Attributes Inspector" tab of IB (it's called "Clip Subviews"), or set the property via code with `view.clipsToBounds = YES;`. – FreeAsInBeer Mar 12 '13 at 02:13
  • The name of the variable is "leftBorder" but it looks like you are actually setting the right border with the code. I already gave it a +1 because I like the way it was solved any how. – TPoschel Apr 02 '13 at 21:15
  • The proper rectangle would be: { 0, 0, width, height }. What is the point in rendering offscreen at -1 position? – pronebird Jul 21 '13 at 14:54
  • 3
    I think that frame code should be `leftBorder.frame = CGRectMake(0, 0, 1, leftScrollView.frame.size.height);` – Krzysztof Romanowski Aug 09 '13 at 15:01
  • i'm glad at least one person here mentioned that this would be plopping the line on the left and not the right... – MrTristan May 16 '14 at 15:38
  • 1
    No, this draws a border on the right, but not on any other edges. However, you can adjust the offsets to get borders on more than one side. This is a neat way to save drawing multiple layers for multiple edges. Needs a bit more explanation to be clear what is actually happening, but this is a good solution. – SeanR Jun 30 '14 at 04:45
  • Consider using rightBorder.frame = CGRectMake(CGRectGetWidth(button.frame) - 1, 0, 1, CGRectGetHeight(button.frame)); if you need borderWidth > 1 – Ramiro Oct 10 '15 at 21:26
18

The easiest way is to add a subLayer that will draw the selective borders, but there are some things to consider when choosing this solution, the biggest are making sure that the borders subLayer is always on top, and that the borders change when you change the frame of your layer.

I implemented a drop in open source solution that takes care of those issues, and let you declare selective borders like this:

myView.borderDirection = AUIFlexibleBordersDirectionRight | AUIFlexibleBordersDirectionTop;

You can get the code, and read about it some more here

adamsiton
  • 3,642
  • 32
  • 34
11

My Swift Solution. It involves four different functions, all extensions to UIView. Each function adds a different border.

extension UIView {
@discardableResult func addRightBorder(color: UIColor, width: CGFloat) -> UIView {
    let layer = CALayer()
    layer.borderColor = color.cgColor
    layer.borderWidth = width
    layer.frame = CGRect(x: self.frame.size.width-width, y: 0, width: width, height: self.frame.size.height)
    self.layer.addSublayer(layer)
    return self
    }
@discardableResult func addLeftBorder(color: UIColor, width: CGFloat) -> UIView {
    let layer = CALayer()
    layer.borderColor = color.cgColor
    layer.borderWidth = width
    layer.frame = CGRect(x: 0, y: 0, width: width, height: self.frame.size.height)
    self.layer.addSublayer(layer)
    return self
    }
@discardableResult func addTopBorder(color: UIColor, width: CGFloat) -> UIView {
    let layer = CALayer()
    layer.borderColor = color.cgColor
    layer.borderWidth = width
    layer.frame = CGRect(x: 0, y: 0, width: self.frame.size.width, height: width)
    self.layer.addSublayer(layer)
    return self
    }
@discardableResult func addBottomBorder(color: UIColor, width: CGFloat) -> UIView {
    let layer = CALayer()
    layer.borderColor = color.cgColor
    layer.borderWidth = width
    layer.frame = CGRect(x: 0, y: self.frame.size.height-width, width: self.frame.size.width, height: width)
    self.layer.addSublayer(layer)
    return self
    }
}
Bryan
  • 1,335
  • 1
  • 16
  • 32
7

The border property always add border to 4 sides of your view. You can make your own draw method to draw the border at the bottom of your view.

But, why don't you just add a view above your UIWebView to make it looks like a border?

xuzhe
  • 5,100
  • 22
  • 28
6

Here's the swift equivalent

leftScrollView.clipsToBounds = true
let rightBorder: CALayer = CALayer()
rightBorder.borderColor = UIColor.darkGrayColor().CGColor
rightBorder.borderWidth = 1
rightBorder.frame = CGRectMake(-1, -1, CGRectGetWidth(leftScrollView.frame), CGRectGetHeight(leftScrollView.frame)+2)
leftScrollView.layer.addSublayer(rightBorder)
conorgriffin
  • 4,282
  • 7
  • 51
  • 88
  • Just tried this and it resulted in a border but the width doesn't match the size of the button I added it to, the border is almost twice as long. – Dave G Jan 25 '16 at 17:53
  • Upvoted this, but you could replace those C-style getters with properties now.. and use CGRect's init method – Doug Mead Jun 20 '17 at 00:47
2

There's another way of doing this. The CAShapeLayer has properties called "strokeStart" and "strokeEnd". Considering the stroking starts from 0 and ends at 1 when it reaches the origin back, thus completing the path, you can set values to start and end to draw the border on one side.

The relative location at which to begin stroking the path. Animatable. The value of this property must be in the range 0.0 to 1.0. The default value of this property is 0.0. Combined with the strokeEnd property, this property defines the subregion of the path to stroke. The value in this property indicates the relative point along the path at which to begin stroking while the strokeEnd property defines the end point. A value of 0.0 represents the beginning of the path while a value of 1.0 represents the end of the path. Values in between are interpreted linearly along the path length.

Example:

let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
let mask = CAShapeLayer()
mask.path = path.CGPath
mask.strokeColor = UIColor.redColor().CGColor
mask.strokeStart = 0.45
mask.strokeEnd = 0.92
self.layer.mask = mask
Anindya Sengupta
  • 2,539
  • 2
  • 21
  • 27
  • 1
    Why go to the all that effort of working out those strange numbers when you're already using a shape layer? Just workout the path for the line, give that to the shape layer + no need for a layer mask – Chris Birch Feb 19 '18 at 23:56
1

The following works with Swift 4.2 for applying a border along an existing UIView.

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

    func addBorder(to side: Side, color: UIColor, borderWidth: CGFloat) {
        let subLayer = CALayer()
        subLayer.borderColor = color.cgColor
        subLayer.borderWidth = borderWidth
        let origin = findOrigin(side: side, borderWidth: borderWidth)
        let size = findSize(side: side, borderWidth: borderWidth)
        subLayer.frame = CGRect(origin: origin, size: size)
        layer.addSublayer(subLayer)
    }

    private func findOrigin(side: Side, borderWidth: CGFloat) -> CGPoint {
        switch side {
        case .right:
            return CGPoint(x: frame.maxX - borderWidth, y: 0)
        case .bottom:
            return CGPoint(x: 0, y: frame.maxY - borderWidth)
        default:
            return .zero
        }
    }

    private func findSize(side: Side, borderWidth: CGFloat) -> CGSize {
        switch side {
        case .left, .right:
            return CGSize(width: borderWidth, height: frame.size.height)
        case .top, .bottom:
            return CGSize(width: frame.size.width, height: borderWidth)
        }
    }
}

And you can call it with this:

myView.addBorder(to: .left, color: .black, borderWidth: 2.0)

What is going on:

  • The user provides a Side as defined in the enum, along with a color and borderWidth
  • A new subLayer is created and given a borderColor and borderWidth
  • The origin is calculated, as determined by the side
  • The size is calculated, also as determined by the side
  • Finally, the layer is given a frame and then added as a sublayer
CodeBender
  • 35,668
  • 12
  • 125
  • 132