185

How do I add dashed line border around UIView.

Something Like this

Wayne Chen
  • 305
  • 2
  • 15
Sohaib
  • 10,941
  • 9
  • 32
  • 34

26 Answers26

276

Another method if you like sublayers. In your custom view's init, put this (_border is an ivar):

_border = [CAShapeLayer layer];
_border.strokeColor = [UIColor colorWithRed:67/255.0f green:37/255.0f blue:83/255.0f alpha:1].CGColor;
_border.fillColor = nil;
_border.lineDashPattern = @[@4, @2];
[self.layer addSublayer:_border];

And in your layoutsubviews, put this:

_border.path = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
_border.frame = self.bounds;
Chris
  • 39,719
  • 45
  • 189
  • 235
164

You can set the border with this pattern using Layer and Bezier path like below examples.

Objective-C

CAShapeLayer *yourViewBorder = [CAShapeLayer layer];
yourViewBorder.strokeColor = [UIColor blackColor].CGColor;
yourViewBorder.fillColor = nil;
yourViewBorder.lineDashPattern = @[@2, @2];
yourViewBorder.frame = yourView.bounds;
yourViewBorder.path = [UIBezierPath bezierPathWithRect:yourView.bounds].CGPath;
[yourView.layer addSublayer:yourViewBorder];

Swift 3.1

var yourViewBorder = CAShapeLayer()
yourViewBorder.strokeColor = UIColor.black.cgColor
yourViewBorder.lineDashPattern = [2, 2]
yourViewBorder.frame = yourView.bounds
yourViewBorder.fillColor = nil
yourViewBorder.path = UIBezierPath(rect: yourView.bounds).cgPath
yourView.layer.addSublayer(yourViewBorder)

You can also set different types of design using pattern image like below example.

[yourView.layer setBorderWidth:5.0];
[yourView.layer setBorderColor:[[UIColor colorWithPatternImage:[UIImage imageNamed:@"DotedImage.png"]] CGColor]];///just add image name and create image with dashed or doted drawing and add here

Here you've to add <QuartzCore/QuartzCore> framework in the project and import it with below line in YourViewController.m file.

#import <QuartzCore/QuartzCore.h>
knshn
  • 3,401
  • 1
  • 21
  • 22
Paras Joshi
  • 20,427
  • 11
  • 57
  • 70
  • 2
    it's not a perfect way when talking about iPhone6 plus. The dotted line could become fuzzy. – LiangWang Jan 12 '15 at 14:31
  • 1
    @Jacky: use a higher resolution image. :) – Olie Feb 11 '15 at 01:27
  • @Olie yes right and use 3x image for iPhone6 plus. – Paras Joshi Feb 11 '15 at 05:13
  • 7
    what's an example of the image? – Tom Roggero Apr 28 '15 at 18:24
  • 1
    can you give a example of the image? – Jonguo Feb 22 '16 at 07:20
  • 1
    Jonguo For example, if you add any image with proper resolution like suppose your normal image size is 120x120 and it's name is test.png then create another two image with name test@2x.png and test@3x.png with size of 240x240 and 360x360 which is used in all the apple device automatically with it's related name. (i.e. test.png is use in iPhone 4, test@2x will useful for iPhone 4s, 5, 5s, 6, 6s and twst@3x will useful for iPhone 6 plus, 6s plus. – Paras Joshi Feb 22 '16 at 07:28
  • Hello @ParasJoshi, what if I have UIView which is of dynamic height. Does this solution, still hold true? – Ranjit Nov 06 '16 at 12:48
  • @Ranjit Yes. You can use this code for that but add this code after set the dynamic height of the UIView. – Paras Joshi Nov 06 '16 at 14:44
  • @ParasJoshi I am trying to create the dotted line pattern but nothing shows. Do you keep all the parameters above for dashed line as well? I set the colorWithPatternImage but nothing is working :( – Gustavo Baiocchi Costa Nov 30 '18 at 17:48
89

For those of you working in Swift, this class extension on UIView makes it easy. This was based on sunshineDev's answer.

extension UIView {
  func addDashedBorder() {
    let color = UIColor.red.cgColor

    let shapeLayer:CAShapeLayer = CAShapeLayer()
    let frameSize = self.frame.size
    let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)

    shapeLayer.bounds = shapeRect
    shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = color
    shapeLayer.lineWidth = 2
    shapeLayer.lineJoin = CAShapeLayerLineJoin.round
    shapeLayer.lineDashPattern = [6,3]
    shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 5).cgPath

    self.layer.addSublayer(shapeLayer)
    }
}

To use it:

anyView.addDashedBorder()
Hardik Thakkar
  • 15,269
  • 2
  • 94
  • 81
rmooney
  • 6,123
  • 3
  • 29
  • 29
  • 5
    Great piece of code! Just one issue, when I apply it to a UIImageView it does not recognise the full width of it, it goes around 80% of it. – arielcr Mar 30 '15 at 17:40
  • 2
    i think you are probably calling addDashBorder too early, try calling it inside didLayoutSubviews – Godfather Apr 17 '15 at 13:15
  • 3
    I created a custom UIView and put this extension inside. Then I called `addDashedBorder()` during `didMoveToSuperview()` thinking autolayout would be complete by then and the frame size would be correct but it was not. The dashed border's width goes beyond the width of the view. The dashed line looks so good by the way! The `self.frame.size` is not correct. – levibostian Jul 31 '15 at 23:42
  • self.layer.masksToBounds=true Use this if it goes out of bounds – Ankish Jain Mar 16 '16 at 07:09
  • @rmooney worked xcellent but the problem is when u add a sublayer in a UITableView dynamic cell the layer considered the layer of storyboard and it is not working in a customcell it is size differ if your storyboard width 375px it will always considered the border by same size and it will not work in a bigger device like 414px and so on. – Azharhussain Shaikh May 04 '19 at 15:22
  • 4
    If anyone is getting an incorrect CGSize on `UITableViewCells` I fixed it by calling `layoutIfNeeded()` on the `contentView` ``` override func layoutSubviews() { super.layoutSubviews() contentView.layoutIfNeeded() } ``` – ahbou Oct 04 '19 at 14:47
  • The solution doesn't work any more in `AutoLayout`, it's only work in the case which view's `height` and `width` have fixed value. – zeleven Nov 23 '21 at 02:22
  • @zeleven anyone ale to make this work on autolayout? – MetaSnarf Apr 06 '22 at 04:36
  • Use like this to work with autolayout `` override func layoutSubviews() { super.layoutSubviews() contentView.layoutIfNeeded() holderView.addDashedBorder() } `` – Sudhakar Varma Apr 21 '23 at 05:13
40

Here is a UIView subclass that can work for any project, it also works for round views:

import UIKit

class CustomDashedView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 0 {
        didSet {
            layer.cornerRadius = cornerRadius
            layer.masksToBounds = cornerRadius > 0
        }
    }
    @IBInspectable var dashWidth: CGFloat = 0
    @IBInspectable var dashColor: UIColor = .clear
    @IBInspectable var dashLength: CGFloat = 0
    @IBInspectable var betweenDashesSpace: CGFloat = 0

    var dashBorder: CAShapeLayer?

    override func layoutSubviews() {
        super.layoutSubviews()
        dashBorder?.removeFromSuperlayer()
        let dashBorder = CAShapeLayer()
        dashBorder.lineWidth = dashWidth
        dashBorder.strokeColor = dashColor.cgColor
        dashBorder.lineDashPattern = [dashLength, betweenDashesSpace] as [NSNumber]
        dashBorder.frame = bounds
        dashBorder.fillColor = nil
        if cornerRadius > 0 {
            dashBorder.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath
        } else {
            dashBorder.path = UIBezierPath(rect: bounds).cgPath
        }
        layer.addSublayer(dashBorder)
        self.dashBorder = dashBorder
    }
}

This way you can edit from the Storyboard like this:

enter image description here

A pair of results:

enter image description here

enter image description here

xavi.pedrals
  • 1,466
  • 14
  • 13
  • 1
    This is the best solution. Works with autolayout, self-sizing views and unlike the other answers, this does not increase dash height when the view height increases (that's a good thing). – Okhan Okbay Nov 23 '20 at 23:58
  • 1
    Very helpful, autolayout also works fine, I added an option to use animation.https://gist.github.com/fhefh2015/96f02d82eb9546c4b51eb79c51d33846 – water_ak47 Jun 24 '21 at 02:04
  • 1
    Better than other solutions. The solutions in other answer work only in the case which view's `height` and `width` have fixed value. But there was a issue that the value set on the view can't preview in the storyboard, any solution? – zeleven Nov 23 '21 at 02:25
  • 1
    Works perfect ... line and dashes are accurate and does not exceed the height and width of view – Azhar Nov 29 '22 at 08:41
  • 1
    Thanks for this excellent solution, although I can point out that on testing this, I noticed that the width of the border was reduced in iOS 14, it was working fine in iOS 15.x and iOS 16.x – Dhruv Saraswat May 01 '23 at 08:47
  • @DhruvSaraswat check it https://stackoverflow.com/a/18006919/467032 – ohtwo Jun 11 '23 at 18:26
30

Swift 3:

import UIKit

class UIViewWithDashedLineBorder: UIView {

    override func draw(_ rect: CGRect) {

        let path = UIBezierPath(roundedRect: rect, cornerRadius: 0)

        UIColor.purple.setFill()
        path.fill()

        UIColor.orange.setStroke()
        path.lineWidth = 5

        let dashPattern : [CGFloat] = [10, 4]
        path.setLineDash(dashPattern, count: 2, phase: 0)
        path.stroke()
    }
}

Use in a storyboard (as custom class) or directly in code:

let v = UIViewWithDashedLineBorder(frame: CGRect(x: 0, y: 0, width: 100, height: 100))

Result:

enter image description here

sash
  • 8,423
  • 5
  • 63
  • 74
  • 4
    Surprise that there are not more upvotes on this. This solution works best with auto-layout comparing to other answers. – Yuchen Nov 10 '19 at 01:48
17

Building upon what Prasad G has suggested I created a method inside a UIImage Extras class with the following:

- (CAShapeLayer *) addDashedBorderWithColor: (CGColorRef) color {
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];

    CGSize frameSize = self.size;

    CGRect shapeRect = CGRectMake(0.0f, 0.0f, frameSize.width, frameSize.height);
    [shapeLayer setBounds:shapeRect];
    [shapeLayer setPosition:CGPointMake( frameSize.width/2,frameSize.height/2)];

    [shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
    [shapeLayer setStrokeColor:color];
    [shapeLayer setLineWidth:5.0f];
    [shapeLayer setLineJoin:kCALineJoinRound];
    [shapeLayer setLineDashPattern:
     [NSArray arrayWithObjects:[NSNumber numberWithInt:10],
      [NSNumber numberWithInt:5],
      nil]];
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:shapeRect cornerRadius:15.0];
    [shapeLayer setPath:path.CGPath];

    return shapeLayer;
}

It's important to point out that if you define your shape's position as (0,0), the bottom corner of the border will be placed in the center of the image, that's why I set it to: (frameSize.width/2,frameSize.height/2)

I then use my method to get the dashed border using the UIImage of my UIImageView and add the CAShapeLayer as a sublayer of the UIImageView layer:

[myImageView.layer addSublayer:[myImageView.image addDashedBorderWithColor:[[UIColor whiteColor] CGColor]]];
sunshineDev
  • 323
  • 3
  • 10
16

Use CGContextSetLineDash() method.

CGFloat dashPattern[]= {3.0, 2};

context =UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
// And draw with a blue fill color
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 4.0);
CGContextSetLineDash(context, 0.0, dashPattern, 2);

CGContextAddRect(context, self.bounds);

// Close the path
CGContextClosePath(context);

CGContextStrokePath(context);

// Fill & stroke the path
CGContextDrawPath(context, kCGPathFillStroke);

I think it will be helpful to you.

richy
  • 2,716
  • 1
  • 33
  • 42
Prasad G
  • 6,702
  • 7
  • 42
  • 65
10

For this you need add CAShapeLayer for that particular object

 CAShapeLayer * dotborder = [CAShapeLayer layer];
    dotborder.strokeColor = [UIColor redColor].CGColor;//your own color
    dotborder.fillColor = nil;
    dotborder.lineDashPattern = @[@4, @2];//your own patten 
    [codeBtn.layer addSublayer:dotborder];
    dotborder.path = [UIBezierPath bezierPathWithRect:codeBtn.bounds].CGPath;
    dotborder.frame = codeBtn.bounds;
btmanikandan
  • 1,923
  • 2
  • 25
  • 45
10

Swift 4.2

Based off rmooney's answer as a UIView extension with configurable parameters that have default values set.

Note this does not work if the view has self.translatesAutoresizingMaskIntoConstraints = false

extension UIView {
  func addDashedBorder(_ color: UIColor = UIColor.black, withWidth width: CGFloat = 2, cornerRadius: CGFloat = 5, dashPattern: [NSNumber] = [3,6]) {

    let shapeLayer = CAShapeLayer()

    shapeLayer.bounds = bounds
    shapeLayer.position = CGPoint(x: bounds.width/2, y: bounds.height/2)
    shapeLayer.fillColor = nil
    shapeLayer.strokeColor = color.cgColor
    shapeLayer.lineWidth = width
    shapeLayer.lineJoin = CAShapeLayerLineJoin.round // Updated in swift 4.2
    shapeLayer.lineDashPattern = dashPattern
    shapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius).cgPath

    self.layer.addSublayer(shapeLayer)
  }
}
Dan Leonard
  • 3,325
  • 1
  • 20
  • 32
  • `shapeLayer.lineJoin = CAShapeLayerLineJoin.round` should be `yourViewBorder.lineJoin = kCALineJoinRound` – pw2 Jun 21 '19 at 09:31
  • 2
    Does it work with autolayout? because it's not working for me – Jalil Aug 26 '21 at 18:30
  • @Jalil yes it should but you many need to already have the width and height of the view you are applying it to before you add the dashed line. since it is based on the views bounds. – Dan Leonard Dec 13 '21 at 19:22
8

• Swift 5

• Works with autolayout

• Works with the corner radius

import UIKit

    class DashedBorderView: UIView {

    private let dashedLineColor = UIColor.black.cgColor
    private let dashedLinePattern: [NSNumber] = [6, 3]
    private let dashedLineWidth: CGFloat = 4

    private let borderLayer = CAShapeLayer()

    init() {
        super.init(frame: CGRect.zero)

        borderLayer.strokeColor = dashedLineColor
        borderLayer.lineDashPattern = dashedLinePattern
        borderLayer.backgroundColor = UIColor.clear.cgColor
        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.lineWidth = dashedLineWidth
        layer.addSublayer(borderLayer)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {
        borderLayer.frame = bounds
        borderLayer.path = UIBezierPath(roundedRect: rect, cornerRadius: layer.cornerRadius).cgPath
    }
}
OhhhThatVarun
  • 3,981
  • 2
  • 26
  • 49
skornos
  • 3,121
  • 1
  • 26
  • 30
6

Swift version of the QuartzCore answer.

import QuartzCore    

let dottedPattern = UIImage(named: "dottedPattern")
myView.layer.borderWidth = 1
myView.layer.borderColor = UIColor(patternImage: dottedPattern!).CGColor

The CAShapeLayer approach works, but the QuartzCore approach is better at handling a Table View reload, if the UIView is inside a cell.

For the image, you can use something like this (it's really small):

enter image description here

I tend to prefer vector over PNGs when I can get away with it:

  • Within Sketch, create a 4x4 pixel rectangle.
  • Make a total of four of these
  • Group them into a foursquare, alternating colors
  • Export the group as PDF
  • Within Images.xcassets, create a New Image Set called dottedPattern
  • Change the Scale Factors to Single Vector
  • Drop in your PDF
Robert Chen
  • 5,179
  • 3
  • 34
  • 21
6

For Xamarin.iOS dashed/dotted border.

        dottedLayer = new CAShapeLayer();
        dottedLayer.StrokeColor = UIColor.FromRGB(202, 202, 208).CGColor; 
        dottedLayer.FillColor = null;
        dottedLayer.LineDashPattern = new[] { new NSNumber(4), new NSNumber(2) };

        dottedLayer.Path = UIBezierPath.FromRect(YourView.Bounds).CGPath; //for square
        dottedLayer.Path = UIBezierPath.FromRoundedRect(YourView.Bounds, 5).CGPath; //for rounded corners

        dottedLayer.Frame = YourView.Bounds;
        YourView.Layer.AddSublayer(dottedLayer);
c.lamont.dev
  • 693
  • 1
  • 6
  • 14
6

In Swift 3

let border = CAShapeLayer();
border.strokeColor = UIColor.black.cgColor;
border.fillColor = nil;
border.lineDashPattern = [4, 4];
border.path = UIBezierPath(rect: theView.bounds).cgPath
border.frame = theView.bounds;
theView.layer.addSublayer(border);
ddb
  • 2,423
  • 7
  • 28
  • 38
6

For Swift 5

extension UIView {
    func addDashBorder() {
        let color = UIColor.white.cgColor

        let shapeLayer:CAShapeLayer = CAShapeLayer()

        let frameSize = self.frame.size
        let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)

        shapeLayer.bounds = shapeRect
        shapeLayer.name = "DashBorder"
        shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = color
        shapeLayer.lineWidth = 1.5
        shapeLayer.lineJoin = .round
        shapeLayer.lineDashPattern = [2,4]
        shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 10).cgPath

        self.layer.masksToBounds = false

        self.layer.addSublayer(shapeLayer)
    }
}

How to add

vw.addDashBorder()

How to remove border again

let _ = vw.layer.sublayers?.filter({$0.name == "DashBorder"}).map({$0.removeFromSuperlayer()})
Hardik Thakkar
  • 15,269
  • 2
  • 94
  • 81
  • 1
    I have added this to a UIImageView that is within a collection view cell and it only covers 70% of the image view. – wxcoder Oct 29 '20 at 06:23
5

Swift 4,5 :- addDashedBorder

extension UIView {
    func setCellDeshBorder(color: UIColor = .appBlue?.withAlphaComponent(0.50) ?? UIColor()) {
        let shapeLayer:CAShapeLayer = CAShapeLayer()
        let frameSize = self.frame.size
        let shapeRect = CGRect(x: 0, y: 0, width: frameSize.width, height: frameSize.height)
        shapeLayer.bounds = shapeRect
        shapeLayer.name = "DashBorder"
        shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = color.cgColor
        shapeLayer.lineWidth = 1.5
        shapeLayer.lineJoin = .round
        shapeLayer.lineDashPattern = [2,4]
        shapeLayer.path = UIBezierPath(roundedRect: shapeRect, cornerRadius: 10).cgPath
        self.layer.masksToBounds = false
        self.layer.addSublayer(shapeLayer)
    }
    
    func removeCellDeshBorder() {
        _ = self.layer.sublayers?.filter({$0.name == "DashBorder"}).map({$0.removeFromSuperlayer()})
    }
}

enter image description here

4

This is if you wanted it in Swift 2

func addDashedLineBorderWithColor(color:UIColor) {
    let _ = self.sublayers?.filter({$0.name == "DashedBorder"}).map({$0.removeFromSuperlayer()})
    let  border = CAShapeLayer();
    border.name = "DashedBorder"
    border.strokeColor = color.CGColor;
    border.fillColor = nil;
    border.lineDashPattern = [4, 4];
    border.path = UIBezierPath(rect: self.bounds).CGPath
    border.frame = self.bounds;
    self.addSublayer(border);

}
3

try bellow code

- (void)drawRect:(CGRect)rect {
    //// Color Declarations
    UIColor* fillColor = [UIColor colorWithRed: 1 green: 1 blue: 1 alpha: 1];
    UIColor* strokeColor = [UIColor colorWithRed: 0.29 green: 0.565 blue: 0.886 alpha: 1];

    //// Rectangle Drawing
    UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius: 6];
    [fillColor setFill];
    [rectanglePath fill];
    [strokeColor setStroke];
    rectanglePath.lineWidth = 1;
    CGFloat rectanglePattern[] = {6, 2, 6, 2};
    [rectanglePath setLineDash: rectanglePattern count: 4 phase: 0];
    [rectanglePath stroke];
    [super drawRect:rect];
}

for one like bellow enter image description here

Shabeer Ali
  • 903
  • 11
  • 20
3

Swift solution with custom class worked with autolayout

customized from @Iain Smith

class DashedBorderView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 4
    @IBInspectable var borderColor: UIColor = UIColor.black
    @IBInspectable var dashPaintedSize: Int = 2
    @IBInspectable var dashUnpaintedSize: Int = 2

    let dashedBorder = CAShapeLayer()

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }

    private func commonInit() {
        //custom initialization
        self.layer.addSublayer(dashedBorder)
        applyDashBorder()
    }

    override func layoutSublayers(of layer: CALayer) {
        super.layoutSublayers(of: layer)
        applyDashBorder()
    }

    func applyDashBorder() {
        dashedBorder.strokeColor = borderColor.cgColor
        dashedBorder.lineDashPattern = [NSNumber(value: dashPaintedSize), NSNumber(value: dashUnpaintedSize)]
        dashedBorder.fillColor = nil
        dashedBorder.cornerRadius = cornerRadius
        dashedBorder.path = UIBezierPath(rect: self.bounds).cgPath
        dashedBorder.frame = self.bounds
    }
}
Marosdee Uma
  • 719
  • 10
  • 14
3
extension UIView{
func addDashedLineBorder() {
    let color = UIColor.black.cgColor

    let shapeLayer:CAShapeLayer = CAShapeLayer()
    let frameSize = (self.frame.size)
    let shapeRect = CGRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height)

    shapeLayer.bounds = shapeRect
    shapeLayer.position = CGPoint(x: frameSize.width/2, y: frameSize.height/2)
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = color
    shapeLayer.lineWidth = 1
    shapeLayer.lineJoin = kCALineJoinRound
    shapeLayer.lineDashPattern = [2,2]
    shapeLayer.path = UIBezierPath(rect: shapeRect).cgPath

    self.layer.addSublayer(shapeLayer)
}

} and call this function in viewdidLoad() with delay:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 
            // Your code with delay
            self.YourView.addDashedBorder()
        }
Ayush Dixit
  • 467
  • 4
  • 10
2

I ended up creating a IB Designable using some of @Chris implementation:

CurvedDashedBorderUIVIew.h:

#import <UIKit/UIKit.h>

IB_DESIGNABLE
@interface CurvedDashedBorderUIVIew : UIView

@property (nonatomic) IBInspectable CGFloat cornerRadius;
@property (nonatomic) IBInspectable UIColor *borderColor;
@property (nonatomic) IBInspectable int dashPaintedSize;
@property (nonatomic) IBInspectable int dashUnpaintedSize;

@property (strong, nonatomic) CAShapeLayer *border;

@end

CurvedDashedBorderUIVIew.m:

#import "CurvedDashedBorderUIVIew.h"

@implementation CurvedDashedBorderUIVIew

- (instancetype)init
{
    self = [super init];
    if (self) {
        [self setup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self setup];
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setup];
    }
    return self;
}

-(void)setup
{
    _border = [CAShapeLayer layer];
    [self.layer addSublayer:_border];
}

-(void)layoutSubviews {
    [super layoutSubviews];
    self.layer.cornerRadius = self.cornerRadius;

    _border.strokeColor = self.borderColor.CGColor;
    _border.fillColor = nil;
    _border.lineDashPattern = @[[NSNumber numberWithInt:_dashPaintedSize],
                                [NSNumber numberWithInt:_dashUnpaintedSize]];
    _border.path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:self.cornerRadius].CGPath;
    _border.frame = self.bounds;
}

@end

then just set it up in the xib/storyboard:

enter image description here

Iain Smith
  • 9,230
  • 4
  • 50
  • 61
2

You can simply create a IBDesignable class like this:

import UIKit

@IBDesignable
class BorderedView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 0

    @IBInspectable var borderWidth: CGFloat = 0

    @IBInspectable var borderColor: UIColor = UIColor.clear

    override func draw(_ rect: CGRect) {
        let path = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
        path.lineWidth = borderWidth

        borderColor.setStroke()

        let dashPattern : [CGFloat] = [10, 4]
        path.setLineDash(dashPattern, count: 2, phase: 0)
        path.stroke()
    }

}

Then just subclass your view with BorderedView from Xcode. This way you can set the border color and border width very easily from the interface builder!

1

Swift 5+

import UIKit

class DashedBorderView: UIView {

    private let borderLayer = CAShapeLayer()

    init(color: UIColor, width: CGFloat = 1) {
        super.init(frame: CGRect.zero)

        let pattern: [NSNumber] = [NSNumber(value: Float(5 * width)), NSNumber(value: Float(3 * width))]

        borderLayer.backgroundColor = nil
        borderLayer.fillColor = nil
        borderLayer.lineDashPattern = pattern
        borderLayer.lineWidth = width
        borderLayer.strokeColor = color.cgColor

        layer.addSublayer(borderLayer)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func draw(_ rect: CGRect) {
        borderLayer.frame = bounds
        borderLayer.path = UIBezierPath(roundedRect: rect, cornerRadius: layer.cornerRadius).cgPath
    }
}

How to use:

// f.e. inside UIViewController

let viewWithDashedBorder = DashedBorderView(color: .red, width: 2)
view.addSubview(viewWithDashedBorder)
gondo
  • 979
  • 1
  • 10
  • 29
0

In swift 4 I created an UIView extension with the following function:

func borderDash(withRadius cornerRadius: Float, borderWidth: Float, borderColor: UIColor, dashSize: Int) {
    let currentFrame = self.bounds
    let shapeLayer = CAShapeLayer()
    let path = CGMutablePath()
    let radius = CGFloat(cornerRadius)

    // Points - Eight points that define the round border. Each border is defined by two points.
    let topLeftPoint = CGPoint(x: radius, y: 0)
    let topRightPoint = CGPoint(x: currentFrame.size.width - radius, y: 0)
    let middleRightTopPoint = CGPoint(x: currentFrame.size.width, y: radius)
    let middleRightBottomPoint = CGPoint(x: currentFrame.size.width, y: currentFrame.size.height - radius)
    let bottomRightPoint = CGPoint(x: currentFrame.size.width - radius, y: currentFrame.size.height)
    let bottomLeftPoint = CGPoint(x: radius, y: currentFrame.size.height)
    let middleLeftBottomPoint = CGPoint(x: 0, y: currentFrame.size.height - radius)
    let middleLeftTopPoint = CGPoint(x: 0, y: radius)

    // Points - Four points that are the center of the corners borders.
    let cornerTopRightCenter = CGPoint(x: currentFrame.size.width - radius, y: radius)
    let cornerBottomRightCenter = CGPoint(x: currentFrame.size.width - radius, y: currentFrame.size.height - radius)
    let cornerBottomLeftCenter = CGPoint(x: radius, y: currentFrame.size.height - radius)
    let cornerTopLeftCenter = CGPoint(x: radius, y: radius)

    // Angles - The corner radius angles.
    let topRightStartAngle = CGFloat(Double.pi * 3 / 2)
    let topRightEndAngle = CGFloat(0)
    let bottomRightStartAngle = CGFloat(0)
    let bottmRightEndAngle = CGFloat(Double.pi / 2)
    let bottomLeftStartAngle = CGFloat(Double.pi / 2)
    let bottomLeftEndAngle = CGFloat(Double.pi)
    let topLeftStartAngle = CGFloat(Double.pi)
    let topLeftEndAngle = CGFloat(Double.pi * 3 / 2)

    // Drawing a border around a view.
    path.move(to: topLeftPoint)
    path.addLine(to: topRightPoint)
    path.addArc(center: cornerTopRightCenter,
                radius: radius,
                startAngle: topRightStartAngle,
                endAngle: topRightEndAngle,
                clockwise: false)
    path.addLine(to: middleRightBottomPoint)
    path.addArc(center: cornerBottomRightCenter,
                radius: radius,
                startAngle: bottomRightStartAngle,
                endAngle: bottmRightEndAngle,
                clockwise: false)
    path.addLine(to: bottomLeftPoint)
    path.addArc(center: cornerBottomLeftCenter,
                radius: radius,
                startAngle: bottomLeftStartAngle,
                endAngle: bottomLeftEndAngle,
                clockwise: false)
    path.addLine(to: middleLeftTopPoint)
    path.addArc(center: cornerTopLeftCenter,
                radius: radius,
                startAngle: topLeftStartAngle,
                endAngle: topLeftEndAngle,
                clockwise: false)

    // Path is set as the shapeLayer object's path.
    shapeLayer.path = path;
    shapeLayer.backgroundColor = UIColor.clear.cgColor
    shapeLayer.frame = currentFrame
    shapeLayer.masksToBounds = false
    shapeLayer.setValue(0, forKey: "isCircle")
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = borderColor.cgColor
    shapeLayer.lineWidth = CGFloat(borderWidth)
    shapeLayer.lineDashPattern = [NSNumber(value: dashSize), NSNumber(value: dashSize)]
    shapeLayer.lineCap = kCALineCapRound

    self.layer.addSublayer(shapeLayer)
    self.layer.cornerRadius = radius;
}
Gastón Antonio Montes
  • 2,559
  • 2
  • 12
  • 15
0

If you want this to work with cornerRadius then try this

tagView.clipsToBounds = YES;
tagView.layer.cornerRadius = 20.0f;
tagView.backgroundColor = [UIColor groupTableViewBackgroundColor];

CAShapeLayer *yourViewBorder = [CAShapeLayer layer];
yourViewBorder.strokeColor = [UIColor blackColor].CGColor;
yourViewBorder.fillColor = nil;
yourViewBorder.lineDashPattern = @[@2, @2];
yourViewBorder.frame = tagView.bounds;

// Create the path for to make circle
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:tagView.bounds
                                               byRoundingCorners:UIRectCornerAllCorners
                                                     cornerRadii:CGSizeMake(20, 20)];


yourViewBorder.path = maskPath.CGPath;

[tagView.layer addSublayer:yourViewBorder];
Clement Joseph
  • 1,235
  • 2
  • 13
  • 17
0

Improvement for @Chris's answer.

extension UIView {
    func dashLinedBorder() -> CAShapeLayer {
        let viewBorder = CAShapeLayer()
        viewBorder.strokeColor = UIColor.black.cgColor
        viewBorder.lineDashPattern = [4, 2]
        viewBorder.fillColor = nil
        self.layer.addSublayer(viewBorder)
        return viewBorder
    }
}

Define your CAShapeLayer inside the ViewController,

var viewBillingProofCAShapeLayer: CAShapeLayer!

override func viewDidLoad() {
    self.viewBillingProofCAShapeLayer = self.viewBillingProofInner.dashLinedBorder()
}

then override viewDidLayoutSubviews

override func viewDidLayoutSubviews() {
    self.viewBillingProofCAShapeLayer.path = UIBezierPath(roundedRect: self.viewBillingProofInner.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: 4, height: 4)).cgPath
    self.viewBillingProofCAShapeLayer.frame = self.viewBillingProofInner.bounds
}
Wimukthi Rajapaksha
  • 961
  • 1
  • 11
  • 23
-3

SwiftUI

   var body: some View {
        Rectangle()
            .strokeBorder(style: StrokeStyle(lineWidth: 4, dash: [10]))
    }

enter image description here

Toseef Khilji
  • 17,192
  • 12
  • 80
  • 121