How do you draw a line programmatically from a view controller?
IosObjective CUiviewUikitIos Problem Overview
I have a UIViewController
. How do I draw a line in one of its programmatically created views?
Ios Solutions
Solution 1 - Ios
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 its path
programmatically.
Solution 2 - Ios
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 - Ios
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 - Ios
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 - Ios
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!
Solution 6 - Ios
You shouldn't really, but if for some reason it makes sense to you, you could create a subclass of UIView, called DelegateDrawView
for example, that takes a delegate which implements a method like
- (void)delegateDrawView:(DelegateDrawView *)aDelegateDrawView drawRect:(NSRect)dirtyRect
and then in the methods - [DelegateDrawView drawRect:]
you should call your delegate method.
But why would you want to put view code in your controller.
You are better off creating a subclass of UIView, that draws a line between two of its corners, you can have a property to set which two and then position the view where you want it from you view controller.
Solution 7 - Ios
To draw inside your view is very simple ,@Mr.ROB said 2 method i took the first method .
Just Copy paste the code where you guys want it.
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
startingPoint = [touch locationInView:self.view];
NSLog(@"Touch starting point = x : %f Touch Starting Point = y : %f", touchPoint.x, touchPoint.y);
}
-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
}
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
touchPoint = [touch locationInView:self.view];
NSLog(@"Touch end point =x : %f Touch end point =y : %f", touchPoint.x, touchPoint.y);
}
-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [[event allTouches] anyObject];
touchPoint = [touch locationInView:self.view];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(touchPoint.x,touchPoint.y)];
[path addLineToPoint:CGPointMake(startingPoint.x,startingPoint.y)];
startingPoint=touchPoint;
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [[UIColor blueColor] CGColor];
shapeLayer.lineWidth = 3.0;
shapeLayer.fillColor = [[UIColor redColor] CGColor];
[self.view.layer addSublayer:shapeLayer];
NSLog(@"Touch moving point =x : %f Touch moving point =y : %f", touchPoint.x, touchPoint.y);
[self.view setNeedsDisplay];
}
- (void)tapGestureRecognizer:(UIGestureRecognizer *)recognizer {
CGPoint tappedPoint = [recognizer locationInView:self.view];
CGFloat xCoordinate = tappedPoint.x;
CGFloat yCoordinate = tappedPoint.y;
NSLog(@"Touch Using UITapGestureRecognizer x : %f y : %f", xCoordinate, yCoordinate);
}
It will draw like a line ,where the finger moves going
Solution 8 - Ios
Swift 5.4
Use a UIView with a 1 or 2 points height and add it as a subview of your view controller's view.
class Separator: UIView {
let line = UIView()
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
backgroundColor = .red
addSubview(line)
line.translatesAutoresizingMaskIntoConstraints = false
line.backgroundColor = .secondaryLabelColor
NSLayoutConstraint.activate([
line.centerYAnchor.constraint(equalTo: self.centerYAnchor),
line.centerXAnchor.constraint(equalTo: self.centerXAnchor),
line.heightAnchor.constraint(equalToConstant: Pad.separatorHeight),
line.widthAnchor.constraint(equalTo: self.widthAnchor, multiplier: 0.8 )
])
}
}
Then add it in your view controller:
let separator = Separator()
view.addSubview(separator)
separator.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
separator.trailingAnchor.constraint(equalTo: view.trailingAnchor),
separator.topAnchor.constraint(equalTo: view.bottomAnchor),
separator.leadingAnchor.constraint(equalTo: view.leadingAnchor),
separator.heightAnchor.constraint(equalToConstant: 72.0)
])