How do you draw a line programmatically from a view controller?

IosObjective CUiviewUikit

Ios 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.

  1. 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 that UIBezierPath:

         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).

  1. The other approach is to subclass UIView and then use CoreGraphics calls in the drawRect method:
  • Create a UIView subclass and define a drawRect 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:

  1. 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)
    
  2. 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)
	])

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
Questionmkc842View Question on Stackoverflow
Solution 1 - IosRobView Answer on Stackoverflow
Solution 2 - IosJeff AmesView Answer on Stackoverflow
Solution 3 - IosBaraa Al-TabbaaView Answer on Stackoverflow
Solution 4 - IosPierre HoustonView Answer on Stackoverflow
Solution 5 - IoskhunshanView Answer on Stackoverflow
Solution 6 - IosNathan DayView Answer on Stackoverflow
Solution 7 - IosKishore KumarView Answer on Stackoverflow
Solution 8 - IosmultitudesView Answer on Stackoverflow