How do you draw a line programmatically from a view controller?
Solution 1
There are two common techniques.
-
Using
CAShapeLayer
:-
Create a
UIBezierPath
(replace the coordinates with whatever you want):UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(10.0, 10.0)]; [path addLineToPoint:CGPointMake(100.0, 100.0)];
-
Create a
CAShapeLayer
that uses thatUIBezierPath
:CAShapeLayer *shapeLayer = [CAShapeLayer layer]; shapeLayer.path = [path CGPath]; shapeLayer.strokeColor = [[UIColor blueColor] CGColor]; shapeLayer.lineWidth = 3.0; shapeLayer.fillColor = [[UIColor clearColor] CGColor];
-
Add that
CAShapeLayer
to your view's layer:[self.view.layer addSublayer:shapeLayer];
In previous versions of Xcode, you had to manually add QuartzCore.framework to your project's "Link Binary with Libraries" and import the
<QuartzCore/QuartzCore.h>
header in your .m file, but that's not necessary anymore (if you have the "Enable Modules" and "Link Frameworks Automatically" build settings turned on). -
-
The other approach is to subclass
UIView
and then use CoreGraphics calls in thedrawRect
method:-
Create a
UIView
subclass and define adrawRect
that draws your line.You can do this with Core Graphics:
- (void)drawRect:(CGRect)rect { CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetStrokeColorWithColor(context, [[UIColor blueColor] CGColor]); CGContextSetLineWidth(context, 3.0); CGContextMoveToPoint(context, 10.0, 10.0); CGContextAddLineToPoint(context, 100.0, 100.0); CGContextDrawPath(context, kCGPathStroke); }
Or using
UIKit
:- (void)drawRect:(CGRect)rect { UIBezierPath *path = [UIBezierPath bezierPath]; [path moveToPoint:CGPointMake(10.0, 10.0)]; [path addLineToPoint:CGPointMake(100.0, 100.0)]; path.lineWidth = 3; [[UIColor blueColor] setStroke]; [path stroke]; }
-
Then you can either use this view class as the base class for your NIB/storyboard or view, or you can have your view controller programmatically add it as a subview:
PathView *pathView = [[PathView alloc] initWithFrame:self.view.bounds]; pathView.backgroundColor = [UIColor clearColor]; [self.view addSubview: pathView];
-
The Swift renditions of the two above approaches are as follows:
-
CAShapeLayer
:// create path let path = UIBezierPath() path.move(to: CGPoint(x: 10, y: 10)) path.addLine(to: CGPoint(x: 100, y: 100)) // Create a `CAShapeLayer` that uses that `UIBezierPath`: let shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.strokeColor = UIColor.blue.cgColor shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineWidth = 3 // Add that `CAShapeLayer` to your view's layer: view.layer.addSublayer(shapeLayer)
-
UIView
subclass:class PathView: UIView { var path: UIBezierPath? { didSet { setNeedsDisplay() } } var pathColor: UIColor = .blue { didSet { setNeedsDisplay() } } override func draw(_ rect: CGRect) { // stroke the path pathColor.setStroke() path?.stroke() } }
And add it to your view hierarchy:
let pathView = PathView() pathView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(pathView) NSLayoutConstraint.activate([ pathView.leadingAnchor.constraint(equalTo: view.leadingAnchor), pathView.trailingAnchor.constraint(equalTo: view.trailingAnchor), pathView.topAnchor.constraint(equalTo: view.topAnchor), pathView.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) pathView.backgroundColor = .clear let path = UIBezierPath() path.move(to: CGPoint(x: 10, y: 10)) path.addLine(to: CGPoint(x: 100, y: 100)) path.lineWidth = 3 pathView.path = path
Above, I'm adding
PathView
programmatically, but you can add it via IB, too, and just set itspath
programmatically.
Solution 2
Create a UIView and add it as a subview of your view controller's view. You can modify this subview's height or width to be very small so that it looks like a line. If you need to draw a diagonal line you can modify the subviews transform property.
e.g. draw black horizontal line. This is called from within your view controller's implementation
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(0,0, self.view.frame.size.width, 1)];
lineView.backgroundColor = [UIColor blackColor];
[self.view addSubview:lineView];
Solution 3
Swift 3:
let path = UIBezierPath()
path.move(to: CGPoint(x: 10, y: 10))
path.addLine(to: CGPoint(x: 100, y: 100))
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.strokeColor = UIColor.blue.cgColor
shapeLayer.lineWidth = 3.0
view.layer.addSublayer(shapeLayer)
Solution 4
Here's a cool technique you might find useful: Using blocks for drawing to avoid subclassing in Objective-C
Include the article's general-purpose view subclass in your project, then this is the kind of code you can put in your view controller to create a view on the fly that draws a line:
DrawView* drawableView = [[[DrawView alloc] initWithFrame:CGRectMake(0,0,320,50)] autorelease];
drawableView.drawBlock = ^(UIView* v,CGContextRef context)
{
CGPoint startPoint = CGPointMake(0,v.bounds.size.height-1);
CGPoint endPoint = CGPointMake(v.bounds.size.width,v.bounds.size.height-1);
CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
CGContextSetLineWidth(context, 1);
CGContextMoveToPoint(context, startPoint.x + 0.5, startPoint.y + 0.5);
CGContextAddLineToPoint(context, endPoint.x + 0.5, endPoint.y + 0.5);
CGContextStrokePath(context);
};
[self.view addSubview:drawableView];
Solution 5
You can use UIImageView to draw lines on.
It however, allows to skip sub-classing. And as I am little inclined to Core Graphics still can use it. You can just put it in - ViewDidLoad
UIGraphicsBeginImageContext(self.view.frame.size);
[self.myImageView.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), brush);
CGContextMoveToPoint(UIGraphicsGetCurrentContext(), 50, 50);
CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), 200, 200);
CGContextStrokePath(UIGraphicsGetCurrentContext());
CGContextFlush(UIGraphicsGetCurrentContext());
self.myImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Addition to Rob's answer, For a quicke, the third approach is to use an UIImageView
- cover up with it - the view of xib. (Thats the default UIImageView appearance when dragged on xib in xcode 5)
Cheers and +1!
Related videos on Youtube
mkc842
Updated on February 26, 2021Comments
-
mkc842 about 3 years
I have a
UIViewController
. How do I draw a line in one of its programmatically created views?-
Bryan Chen almost 11 yearsNo you can't. Drawing should be done by view not controller. You have to change your design.
-
mkc842 almost 11 years@xlc You CAN do it from a VC, as Rob's answer shows. If you actually want to be helpful, explain what's the harm in the bezier technique.
-
bugmagnet almost 10 yearsCan we see some code? I agree, it's a a real question, but I (maybe we) respond best to code demonstrating what you're talking about.
-
-
marko almost 11 yearsI'd be inclined to agree that for a separator element (e.g. an line between UI elements) this is the path of least resistance. The alternative of either rendering into a bitmap to back a CoreAnimation layer or implementing a draw handler in a custom view is far more involved. This probably hardware accelerates better too, provided it's only one line.
-
Jeff Ames about 10 yearsI'm curious about the reasoning of why this is is a terrible idea. I'd like to avoid any problems this solution might cause in my own code.
-
Manthan over 9 years@Rob: This works perfectly fine. I want to ask you that I have a line distance AB and an angle from AB, then how to get start and end points in that situation? Please help me.
-
Kishore Kumar over 8 yearsi added uigesture to my view ,and i have touch starting point and moving point if i add that in above comment straight line working properly .but when i touch at side from starting point itself its occupying the color @Rob
-
Joe Huang about 8 years@sangony I use this technique too in my code. I also wonder why this is a terrible idea? Please help to explain.
-
Albert Renshaw over 6 yearsWhen using CAShapeLayer method you can (and likely will want to) set the line caps to rounded so that lines look smooth as they connect,
shapeLayer.lineCap = kCALineCapRound;
-
Rob over 6 yearsOr, if you have multiple lines that you want to look smooth as they connect, you could also have a single
UIBezierPath
, with repeatedaddLineToPoint
/addLine(to:)
calls. Then you can pick whether you prefershapeLayer.lineJoin = kCALineJoinRound
orkCALineJoinBevel
orkCALineJoinMiter
. -
Vilmir over 5 yearsThanks for the code snippet, it saved me quite some time. If you intend to draw a dynamic view, reacting to some events, you will need to clean the sublayer at the beginning of draw with this: self.layer.sublayers = nil