Add a border outside of a UIView (instead of inside)

IosUiviewBorder

Ios Problem Overview


If a add a border of a view using code in a view like

self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = 2.0f;

the border is added inside the view like the following: enter image description here

the right view is the original view, as you can see, the black area of bordered view is less than the original one. but what I want to get is a border outside of original view, like this:enter image description here. the black area is equal to original one, how can I implement it?

Ios Solutions


Solution 1 - Ios

Unfortunately, there isn't simply a little property you can set to align the border to the outside. It draws aligned to the inside because the UIViews default drawing operations draw within its bounds.

The simplest solution that comes to mind would be to expand the UIView by the size of the border width when applying the border:

CGFloat borderWidth = 2.0f;
    
self.frame = CGRectInset(self.frame, -borderWidth, -borderWidth);
self.layer.borderColor = [UIColor yellowColor].CGColor;
self.layer.borderWidth = borderWidth;

Solution 2 - Ios

With the above accepted best answer i made experiences with such not nice results and unsightly edges:

border without bezier path

So i will share my UIView Swift extension with you, that uses a UIBezierPath instead as border outline – without unsightly edges (inspired by @Fattie):

border with bezier path

//  UIView+BezierPathBorder.swift

import UIKit

extension UIView {
    
    fileprivate var bezierPathIdentifier:String { return "bezierPathBorderLayer" }
    
    fileprivate var bezierPathBorder:CAShapeLayer? {
        return (self.layer.sublayers?.filter({ (layer) -> Bool in
            return layer.name == self.bezierPathIdentifier && (layer as? CAShapeLayer) != nil
        }) as? [CAShapeLayer])?.first
    }
    
    func bezierPathBorder(_ color:UIColor = .white, width:CGFloat = 1) {
        
        var border = self.bezierPathBorder
        let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:self.layer.cornerRadius)
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
        
        if (border == nil) {
            border = CAShapeLayer()
            border!.name = self.bezierPathIdentifier
            self.layer.addSublayer(border!)
        }
        
        border!.frame = self.bounds
        let pathUsingCorrectInsetIfAny =
            UIBezierPath(roundedRect: border!.bounds, cornerRadius:self.layer.cornerRadius)
        
        border!.path = pathUsingCorrectInsetIfAny.cgPath
        border!.fillColor = UIColor.clear.cgColor
        border!.strokeColor = color.cgColor
        border!.lineWidth = width * 2
    }

    func removeBezierPathBorder() {
        self.layer.mask = nil
        self.bezierPathBorder?.removeFromSuperlayer()
    }

}

Example:

let view = UIView(frame: CGRect(x: 20, y: 20, width: 100, height: 100))
view.layer.cornerRadius = view.frame.width / 2
view.backgroundColor = .red

//add white 2 pixel border outline
view.bezierPathBorder(.white, width: 2)

//remove border outline (optional)
view.removeBezierPathBorder()

Solution 3 - Ios

Ok, there already is an accepted answer but I think there is a better way to do it, you just have to had a new layer a bit larger than your view and do not mask it to the bounds of the view's layer (which actually is the default behaviour). Here is the sample code :

CALayer * externalBorder = [CALayer layer];
externalBorder.frame = CGRectMake(-1, -1, myView.frame.size.width+2, myView.frame.size.height+2);
externalBorder.borderColor = [UIColor blackColor].CGColor;
externalBorder.borderWidth = 1.0;

[myView.layer addSublayer:externalBorder];
myView.layer.masksToBounds = NO;

Of course this is if you want your border to be 1 unity large, if you want more you adapt the borderWidth and the frame of the layer accordingly. This is better than using a second view a bit larger as a CALayer is lighter than a UIView and you don't have do modify the frame of myView, which is good for instance if myView is aUIImageView

N.B : For me the result was not perfect on simulator (the layer was not exactly at the right position so the layer was thicker on one side sometimes) but was exactly what is asked for on real device.

EDIT

Actually the problem I talk about in the N.B was just because I had reduced the screen of the simulator, on normal size there is absolutely no issue

Hope it helps

Solution 4 - Ios

For a Swift implementation, you can add this as a UIView extension.

extension UIView {
    
    struct Constants {
        static let ExternalBorderName = "externalBorder"
    }
    
    func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.whiteColor()) -> CALayer {
        let externalBorder = CALayer()
        externalBorder.frame = CGRectMake(-borderWidth, -borderWidth, frame.size.width + 2 * borderWidth, frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.CGColor
        externalBorder.borderWidth = borderWidth
        externalBorder.name = Constants.ExternalBorderName
        
        layer.insertSublayer(externalBorder, atIndex: 0)
        layer.masksToBounds = false
        
        return externalBorder
    }
    
    func removeExternalBorders() {
        layer.sublayers?.filter() { $0.name == Constants.ExternalBorderName }.forEach() {
            $0.removeFromSuperlayer()
        }
    }
    
    func removeExternalBorder(externalBorder: CALayer) {
        guard externalBorder.name == Constants.ExternalBorderName else { return }
        externalBorder.removeFromSuperlayer()
    }
    
}

Solution 5 - Ios

Well there is no direct method to do it You can consider some workarounds.

  1. Change and increase the frame and add bordercolor as you did

  2. Add a view behind the current view with the larger size so that it appears as border.Can be worked as a custom class of view

  3. If you dont need a definite border (clearcut border) then you can depend on shadow for the purpose

    [view1 setBackgroundColor:[UIColor blackColor]];
    UIColor *color = [UIColor yellowColor];
    view1.layer.shadowColor = [color CGColor];
    view1.layer.shadowRadius = 10.0f;
    view1.layer.shadowOpacity = 1;
    view1.layer.shadowOffset = CGSizeZero;
    view1.layer.masksToBounds = NO;
    

Solution 6 - Ios

Increase the width and height of view's frame with border width before adding the border:

float borderWidth = 2.0f
CGRect frame = self.frame;
frame.width += borderWidth;
frame.height += borderWidth;
 self.layer.borderColor = [UIColor yellowColor].CGColor;
 self.layer.borderWidth = 2.0f;

Solution 7 - Ios

Swift 5

extension UIView {
    fileprivate struct Constants {
        static let externalBorderName = "externalBorder"
    }

    func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) -> CALayer {
        let externalBorder = CALayer()
        externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.cgColor
        externalBorder.borderWidth = borderWidth
        externalBorder.name = Constants.ExternalBorderName

        layer.insertSublayer(externalBorder, at: 0)
        layer.masksToBounds = false

        return externalBorder
    }

    func removeExternalBorders() {
        layer.sublayers?.filter() { $0.name == Constants.externalBorderName }.forEach() {
            $0.removeFromSuperlayer()
        }
    }

    func removeExternalBorder(externalBorder: CALayer) {
        guard externalBorder.name == Constants.externalBorderName else { return }
        externalBorder.removeFromSuperlayer()
    }
}

Solution 8 - Ios

I liked solution of @picciano If you want exploding circle instead of square replace addExternalBorder function with:

func addExternalBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
        let externalBorder = CALayer()
        externalBorder.frame = CGRect(x: -borderWidth, y: -borderWidth, width: frame.size.width + 2 * borderWidth, height: frame.size.height + 2 * borderWidth)
        externalBorder.borderColor = borderColor.cgColor
        externalBorder.borderWidth = borderWidth
        externalBorder.cornerRadius = (frame.size.width + 2 * borderWidth) / 2
        externalBorder.name = Constants.ExternalBorderName
        layer.insertSublayer(externalBorder, at: 0)
        layer.masksToBounds = false
        
    }

Solution 9 - Ios

There is actually a very simple solution. Just set them both like this:

view.layer.borderWidth = 5
    
view.layer.borderColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.5).cgColor
    
view.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.25).cgColor

Solution 10 - Ios

How I placed a border around my UI view (main - SubscriptionAd) in Storyboard is to place it inside another UI view (background - BackgroundAd). The Background UIView has a background colour that matches the border colour i want, and the Main UIView has constraints value 2 from each side.

I will link the background view to my ViewController and then turn the border on and off by changing the background colour.

Image Of Nested UIView with Top View 2px constraints all around, making it smaller than larger

Solution 11 - Ios

I liked solution of @picciano & @Maksim Kniazev. We can also create annular border with following:

func addExternalAnnularBorder(borderWidth: CGFloat = 2.0, borderColor: UIColor = UIColor.white) {
    let externalBorder = CALayer()
    externalBorder.frame = CGRect(x: -borderWidth*2, y: -borderWidth*2, width: frame.size.width + 4 * borderWidth, height: frame.size.height + 4 * borderWidth)
    externalBorder.borderColor = borderColor.cgColor
    externalBorder.borderWidth = borderWidth
    externalBorder.cornerRadius = (frame.size.width + 4 * borderWidth) / 2
    externalBorder.name = Constants.ExternalBorderName
    layer.insertSublayer(externalBorder, at: 0)
    layer.masksToBounds = false
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionremykitsView Question on Stackoverflow
Solution 1 - IosElliott James PerryView Answer on Stackoverflow
Solution 2 - IosPeter KreinzView Answer on Stackoverflow
Solution 3 - IosHugues DuvillierView Answer on Stackoverflow
Solution 4 - IospiccianoView Answer on Stackoverflow
Solution 5 - IosLithu T.VView Answer on Stackoverflow
Solution 6 - IosHossam GhareebView Answer on Stackoverflow
Solution 7 - IosPacyjentView Answer on Stackoverflow
Solution 8 - IosYaroslav DukalView Answer on Stackoverflow
Solution 9 - Iosuser11141180View Answer on Stackoverflow
Solution 10 - IosJonesJr876View Answer on Stackoverflow
Solution 11 - IosJimbungView Answer on Stackoverflow