How to procedurally draw rectangle / lines in swift using CGContext
Asked Answered
O

3

17

I've been trawling the internet for days trying to find the simplest code examples on how to draw a rectangle or lines procedurally in Swift. I have seen how to do it by overriding the DrawRect command. I believe you can create a CGContext and then drawing into an image, but I'd love to see some simple code examples. Or is this a terrible approach? Thanks.

class MenuController: UIViewController 
{

    override func viewDidLoad() 
    {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        self.view.backgroundColor = UIColor.blackColor()

        var logoFrame = CGRectMake(0,0,118,40)
        var imageView = UIImageView(frame: logoFrame)
        imageView.image = UIImage(named:"Logo")
        self.view.addSubview(imageView)

        //need to draw a rectangle here
    }
}
Oarsman answered 10/8, 2014 at 15:9 Comment(0)
U
35

Here's an example that creates a custom UIImage containing a transparent background and a red rectangle with lines crossing diagonally through it.

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad();

        let imageSize = CGSize(width: 200, height: 200)
        let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: imageSize))
        self.view.addSubview(imageView)
        let image = drawCustomImage(size: imageSize)
        imageView.image = image
    }
}

func drawCustomImage(size: CGSize) -> UIImage {
    // Setup our context
    let bounds = CGRect(origin: .zero, size: size)
    let opaque = false
    let scale: CGFloat = 0
    UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
    let context = UIGraphicsGetCurrentContext()!

    // Setup complete, do drawing here
    context.setStrokeColor(UIColor.red.cgColor)
    context.setLineWidth(2)

    context.stroke(bounds)

    context.beginPath()
    context.move(to: CGPoint(x: bounds.minX, y: bounds.minY))
    context.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
    context.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
    context.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
    context.strokePath()

    // Drawing complete, retrieve the finished image and cleanup
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image!
}
Uncommitted answered 10/8, 2014 at 19:31 Comment(3)
Fantastic, that was just what I was looking for and a very eloquent example. Thank you for your help. Is this an efficient way of drawing, or are there better ways of doing it?Oarsman
I think this is about as efficient as it gets for this sort of drawing with Quartz. You can get some pedantic but probably insignificant efficiencies in the above code by assigning CGRectGetMinX(bounds) to a variable and using the variable repeatedly— again this probably won't result in noticeable gain in speed. You can draw vectors faster with OpenGL, but they're not as flexible or good looking.Uncommitted
Note that if you ever want to overlay another UIImage in this context it will be flipped if you use CGContextDrawImage. You must use image.drawInRect(bounds) instead.Bacteriophage
B
9

An updated answer using Swift 3.0

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad();

        let imageSize = CGSize(width: 200, height: 200)
        let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: imageSize))
        self.view.addSubview(imageView)
        let image = drawCustomImage(size: imageSize)
        imageView.image = image
    }
}

func drawCustomImage(size: CGSize) -> UIImage? {
    // Setup our context
    let bounds = CGRect(origin: CGPoint.zero, size: size)
    let opaque = false
    let scale: CGFloat = 0
    UIGraphicsBeginImageContextWithOptions(size, opaque, scale)
    guard let context = UIGraphicsGetCurrentContext() else { return nil }

    // Setup complete, do drawing here
    context.setStrokeColor(UIColor.red.cgColor)
    context.setLineWidth(5.0)

    // Would draw a border around the rectangle
    // context.stroke(bounds)

    context.beginPath()
    context.move(to: CGPoint(x: bounds.maxX, y: bounds.minY))
    context.addLine(to: CGPoint(x: bounds.minX, y: bounds.maxY))
    context.strokePath()

    // Drawing complete, retrieve the finished image and cleanup
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image
}

let imageSize = CGSize(width: 200, height: 200)
let imageView = UIImageView(frame: CGRect(origin: CGPoint(x: 100, y: 100), size: imageSize))
let image = drawCustomImage(size: imageSize)
imageView.image = image
Beseech answered 26/10, 2016 at 18:50 Comment(2)
I get all sort of errors: "Value of type 'CGContext' has no member 'setStrokeColor', and so on for every method. It seems the method are documented in Apple's API reference, but I am not able to make them work with Swift 3..Scherle
Thanks for providing this. Better than before, but still seems an ugly mix between object oriented programming and otherwise. Oh well, maybe they'll fix it (again) next iteration.Kwangchowan
C
3

I used the accepted answer to draw lines in a Tic Tac Toe game when one of the players won. Thanks, good to know that it worked. Unfortunately, I ran into some problems getting it to work on different sizes of iPhones and iPads simultaneously. That's probably something that should have been addressed. Basically what I'm saying is that it might not actually be worth the trouble of all that code, depending on your case.

My alternate solution is to simply make customized, better looking line in Photoshop and then load it with UIImageView. For me this was MUCH simpler, runs better, and looks better. Obviously it really depends on what you need it for.

Steps:

1: Download or create an image (preferably saved as .PNG)

2: Drag it into your project

3: Drag a UIImage View into your storyboard

4: Click on the Image View and select the image in the attributes inspector

5: Ctrl click and drag the Image View to your .swift file to declare an Outlet

6: Set the autolayout constraints so it works on ALL devices EASILY

Animating, rotating, and transforming image views on and off the screen is also arguably easier

To change the image:

yourImageViewOutletName.image = UIImage(named: "ImageNameHere")
Cantwell answered 29/7, 2015 at 19:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.