UIView with a Dashed line

52,443

Solution 1

Note: The code from Prince did really help me out, so I will give him +10 for the tips. But in the end, I add to come with my own code. I will also add some context to it, so it can be useful for future readers


The final code was like this:

-(void)updateLine{

      // Important, otherwise we will be adding multiple sub layers
      if ([[[self layer] sublayers] objectAtIndex:0])
        {
            self.layer.sublayers = nil;
        }

        CAShapeLayer *shapeLayer = [CAShapeLayer layer];
        [shapeLayer setBounds:self.bounds];
        [shapeLayer setPosition:self.center];
        [shapeLayer setFillColor:[[UIColor clearColor] CGColor]];
        [shapeLayer setStrokeColor:[[UIColor blackColor] CGColor]];
        [shapeLayer setLineWidth:3.0f];
        [shapeLayer setLineJoin:kCALineJoinRound];
        [shapeLayer setLineDashPattern:
        [NSArray arrayWithObjects:[NSNumber numberWithInt:10],
        [NSNumber numberWithInt:5],nil]];

        // Setup the path
        CGMutablePathRef path = CGPathCreateMutable();
        CGPathMoveToPoint(path, NULL, beginPoint.center.x, beginPoint.center.y);
        CGPathAddLineToPoint(path, NULL, endPoint.center.x, endPoint.center.y);

        [shapeLayer setPath:path];
        CGPathRelease(path);

        [[self layer] addSublayer:shapeLayer];
}

In my case, the beginPoint and endPoint are movable by the user, by using KVO. So when one of them moves:

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqual:@"position"])
    {
        [self updateLine];
    }
}

I did play a lot with Prince's code. I tried on the draw: method, which add a thin line between the dashed line (a bit weird...) and I also tried on initWithFrame:. By itself his code, without any modifications, would give me this kind of errors on the console:

<Error>: CGContextSaveGState: invalid context 0x0
<Error>: CGContextSetLineWidth: invalid context 0x0
<Error>: CGContextSetLineJoin: invalid context 0x0
<Error>: CGContextSetLineCap: invalid context 0x0
<Error>: CGContextSetMiterLimit: invalid context 0x0
<Error>: CGContextSetFlatness: invalid context 0x0
<Error>: CGContextAddPath: invalid context 0x0
<Error>: CGContextDrawPath: invalid context 0x0
<Error>: CGContextRestoreGState: invalid context 0x0

Solution 2

Check UIBezierPath setLineDash:count:phase: method:

- (void)setLineDash:(const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase` method. 

This allows you to draw dashed lines.

  1. First add a CAShapeLayer. Add it as sublayer to your UIView. It has a path property.
  2. Now make an object of UIBezierPath. Draw the line using setLineDash.

For example:

 UIBezierPath *path = [UIBezierPath bezierPath];
 //draw a line
 [path moveToPoint:yourStartPoint]; //add yourStartPoint here
 [path addLineToPoint:yourEndPoint];// add yourEndPoint here
 [path stroke];

 CGFloat dashPattern[] = {2.0f,6.0f,4.0f,2.0f}; //make your pattern here
 [path setLineDash:dashPattern count:4 phase:3];

 UIColor *fill = [UIColor blueColor];
 shapelayer.strokeStart = 0.0;
 shapelayer.strokeColor = fill.CGColor;
 shapelayer.lineWidth = 5.0;
 shapelayer.lineJoin = kCALineJoinMiter;
 shapelayer.lineDashPattern = [NSArray arrayWithObjects:[NSNumber numberWithInt:10],[NSNumber numberWithInt:7], nil];
 shapelayer.lineDashPhase = 3.0f;
 shapelayer.path = path.CGPath;

Note: This answer provides a hint so you can improvise accordingly to your requirement(s).

Solution 3

Dash Line in Swift4 • Xcode 9

Crate a CAShapeLayer & use lineDashPattern

extension UIView {

    func addDashedBorder() {
        //Create a CAShapeLayer
        let shapeLayer = CAShapeLayer()
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 2
        // passing an array with the values [2,3] sets a dash pattern that alternates between a 2-user-space-unit-long painted segment and a 3-user-space-unit-long unpainted segment
        shapeLayer.lineDashPattern = [2,3]

        let path = CGMutablePath()
        path.addLines(between: [CGPoint(x: 0, y: 0),
                                CGPoint(x: self.frame.width, y: 0)])
        shapeLayer.path = path
        layer.addSublayer(shapeLayer)
    }
}

Usage:

dashView.addDashedBorder()

Output:

enter image description here

Solution 4

Here is Swift 3 version of Alexandre G's answer https://stackoverflow.com/a/38194152/1800489

extension UIView {

        func addDashedLine(color: UIColor = .lightGray) {
            layer.sublayers?.filter({ $0.name == "DashedTopLine" }).map({ $0.removeFromSuperlayer() })
            backgroundColor = .clear

            let shapeLayer = CAShapeLayer()
            shapeLayer.name = "DashedTopLine"
            shapeLayer.bounds = bounds
            shapeLayer.position = CGPoint(x: frame.width / 2, y: frame.height / 2)
            shapeLayer.fillColor = UIColor.clear.cgColor
            shapeLayer.strokeColor = color.cgColor
            shapeLayer.lineWidth = 1
            shapeLayer.lineJoin = kCALineJoinRound
            shapeLayer.lineDashPattern = [4, 4]

            let path = CGMutablePath()
            path.move(to: CGPoint.zero)
            path.addLine(to: CGPoint(x: frame.width, y: 0))
            shapeLayer.path = path

            layer.addSublayer(shapeLayer)
        }
}

Solution 5

Swift 2.2

dropping this in here to save others time..

extension UIView {
    func addDashedLine(color: UIColor = UIColor.lightGrayColor()) {
        layer.sublayers?.filter({ $0.name == "DashedTopLine" }).map({ $0.removeFromSuperlayer() })
        self.backgroundColor = UIColor.clearColor()
        let cgColor = color.CGColor

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

        shapeLayer.name = "DashedTopLine"
        shapeLayer.bounds = shapeRect
        shapeLayer.position = CGPoint(x: frameSize.width / 2, y: frameSize.height / 2)
        shapeLayer.fillColor = UIColor.clearColor().CGColor
        shapeLayer.strokeColor = cgColor
        shapeLayer.lineWidth = 1
        shapeLayer.lineJoin = kCALineJoinRound
        shapeLayer.lineDashPattern = [4, 4]

        let path: CGMutablePathRef = CGPathCreateMutable()
        CGPathMoveToPoint(path, nil, 0, 0)
        CGPathAddLineToPoint(path, nil, self.frame.width, 0)
        shapeLayer.path = path

        self.layer.addSublayer(shapeLayer)
    }
}
Share:
52,443
Rui Peres
Author by

Rui Peres

The cake is a lie You can also find me here: ruiper.es.

Updated on February 06, 2022

Comments

  • Rui Peres
    Rui Peres about 2 years

    What I have:

    enter image description here

    To create this line, I basically have an UIView and I do the following:

    void setLayerToLineFromAToB(CALayer *layer, CGPoint a, CGPoint b, CGFloat lineWidth)
    {
        CGPoint center = { 0.5 * (a.x + b.x), 0.5 * (a.y + b.y) };
        CGFloat length = sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
        CGFloat angle = atan2(a.y - b.y, a.x - b.x);
    
        layer.position = center;
        layer.bounds = (CGRect) { {0, 0}, { length + lineWidth, lineWidth } };
        layer.transform = CATransform3DMakeRotation(angle, 0, 0, 1);
    }
    

    Note: This code was found here on stackoverflow, so if someone can give me the reference to it I would appreciate.

    What I want:

    enter image description here

    Ok so the "only" thing I need is to create this pattern on the UIView. I know I am able to do this using Quartz2D (a simple way to do it can be found here). But I want to do it by manipulating the CALayer and not going to to the draw method. Why? Because of the transformation I am making on my UIView, I am not able to draw correctly using the draw method.

    Edit 1:

    Just to illustrate my problem:

    enter image description here

    Normally what you have is UIView and then you basically just draw something in it (in this case a simple line). The solution I found to get rid of the "gray" area, was to instead of drawing something, just transform the UIView itself. It work well, if you want a fully filled line, the problem comes when you want a dashed one.