Drawing using Core Graphics [Swift]

Learning to draw content is important in iOS. Even if you have not done it before, it is almost certain you might do it in the future. Drawing helps achieve high quality graphics independent of the device and at the same time provides great performance using the least memory. Drawing in iOS can be done using OpenGL or in native using Quartz, Core Animation or UIKit.

In this guide, we use the Core Graphics Framework which is based on Quartz for drawing in iOS.

The Core Graphics framework is a C-based API that is based on the Quartz advanced drawing engine. It provides low-level, lightweight 2D rendering with unmatched output fidelity. You use this framework to handle path-based drawing, transformations, color management, offscreen rendering, patterns, gradients and shadings, image data management, image creation, masking, and PDF document creation, display, and parsing.

Apple Developer: Core Graphics Framework Reference

All drawings are performed on an UIView object. A UIView subclass is created and the drawing code is added to the drawRect: method by overriding it.

Whenever a view is first displayed on the screen or is updated, iOS prompts the view to draw its contents by calling the drawRect: method. The drawRect: method is called in the following cases:

  • When another view overlaps on to this view/changes its position.
  • When the view is hidden/made visible explicitly or by scrolling it offscreen.
  • Programatically by calling the setNeedsDisplay method on the view.

Note: The drawRect: method should never be called directly on the view. Instead, if the view needs to be re-drawn, call the setNeedsDisplay method.

Before we begin with the drawing, It is better to make yourself familiar with the co-ordinate systems and points/pixel differentiation. You can read about this here: Apple Developer: Drawing and Printing Guide for iOS

Drawing

Drawing onto a view involves the following steps.

(To follow up with this tutorial, you can begin by creating a simple single view application template. Xcode: File -> New -> Project Select iOS: Single View Application)

1. Creating a custom view

For adding the drawing code, create a subclass of UIView by adding a swift file to the project (Xcode: File -> New -> File Select iOS: Source -> Swift File) and call it “simpleView“. Create a subclass of UIView as below and override the drawRect: method for writing the drawing code.

import UIKit

class simpleView: UIView {
    // Override Draw Rect method to perform custom drawing.
    override func drawRect(rect: CGRect) {
    }
}

In the sample project, open the main.storyboard, add a new UIView to the ViewController’s view as a subview and set the class of this view to our custom view simpleView. Set the dimensions of the subview and add any necessary constraints. (For more on Constraints and AutoLayouts, read the post Auto Layouts in iOS.)

2. Obtaining the Graphics Context

For creating/drawing on a view, we need to initially obtain what is known as a Graphics Context. iOS will prepare for drawing based on this context for different destinations such as on-screen, bitmap or PDF. This context will only be available for the lifetime of drawRect: method.

Obtaining the Graphics Context:
	let context = UIGraphicsGetCurrentContext()

A graphics context represents a drawing destination. It contains drawing parameters and all device-specific information that the drawing system needs to perform any subsequent drawing commands.

Apple Developer: Quartz 2D Programming Guide

3. Setting the Graphics Context Parameters

Conceptually, a graphics context is an object that describes where and how drawing should occur, including basic drawing attributes such as the colors to use when drawing, the clipping area, line width and style information, font information, compositing options, and so on.

Apple Developer: Drawing and Printing Guide for iOS

Example for setting the fill color of the context
	CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)

There are a variety of aspects you can set on the graphic context as described above. A list of which is provided in the Apple Developer: CGContext Reference and a glossary of terms is available in the Apple Developer: Core Graphics Framework Reference.

4. Creating Paths

Paths are shapes created using a lines/curves(Bézier Curves) which are vectors. And these paths need to be drawn onto the graphics context for them to be displayed on the screen.

You can use the available convenience methods of UIKit to create simple paths like rectangles/circles. For more complex paths one can make use of the UIBezierPath class to construct the required shape.

Example of creating and drawing/filling the view using convenience methods:
        CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)  // Set fill color
        CGContextFillRect(context, rect) // Fill rectangle using the context data
Example of creating a triangle path using UIBezierPath:
        // Imagine a triangle resting on the bottom of the container with the base as the width of the rectangle, and the apex of the triangle at the top center of the container
        // The co-ordinates of the rectangle will look like
        // Top = (x: half of Container Width, y: 0 - origin)
        // Bottom Left = (x: 0, y: Container Height)
        // Bottom Right = (x: Container Width, y: Container Height)

        // Create path for drawing a triangle
        var trianglePath = UIBezierPath()
        // First move to the Top point
        trianglePath.moveToPoint(CGPoint(x: self.bounds.width/2, y: 0.0))
        // Add line to Bottom Right
        trianglePath.addLineToPoint(CGPoint(x: self.bounds.width, y: self.bounds.height))
        // Add line to Bottom Left
        trianglePath.addLineToPoint(CGPoint(x: 0.0, y: self.bounds.height))
        // Complete path by drawing path to the Top
        trianglePath.addLineToPoint(CGPoint(x: self.bounds.width/2, y: 0.0))

More on Drawing shapes using Bézier Paths here:

5. Drawing Paths

The paths created in the previous steps are only imaginary. i.e., the paths are not drawn onto the screen yet. Paths can be stoked/filled using the below functions:

 CGContextFillPath(c: CGContext!) // Fills the path using the context parameters
 CGContextStrokePath(c: CGContext!) // Draws/strokes the path using the context parameters

Aaand it’s done! 🙂

So far we obtained the context, set it’s parameters, created paths and finally drew on the path. Below are few simple drawing examples illustrating how the above steps come together and complete the drawing.

Examples

1. Stroking and filling Rectangles and Circles

Requirement:

CGExample1

Code:

    override func drawRect(rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()

        // ************************************************************
        // Paint the View Blue
        CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)  // Set fill color
        CGContextFillRect(context, rect) // Fill rectangle using the context data

        // ************************************************************
        // Paint a rect inside view Green
        let greenRect = CGRectInset(rect, 30, 30) // Inset will reduce the size of 'rect' by 30 in x and 30 in y.
        CGContextSetFillColorWithColor(context, UIColor.greenColor().CGColor) // Set fill color
        /* Fill `rect' with the current fill color. */
        CGContextFillRect(context, greenRect) // Fill rectangle using the context data

        // ************************************************************
        // Stroke a white rect inside the inner Green rectangle
        let whiteRect = CGRectInset(greenRect, 30, 30) // Inset will reduce the size of 'rect' by 30 in x and 30 in y.
        CGContextSetStrokeColorWithColor(context, UIColor.whiteColor().CGColor) // Set stroke color: (Note this is different from fill color)
        
//        CGContextSetLineWidth(context, 2.0) // Line width to be used for strokes
        /* Stroke `rect' with the current stroke color and the current linewidth. */
//        CGContextStrokeRect(context, whiteRect) // Fill rectangle using the context data
        
        // Alternate to the above two lines
        /* Stroke `rect' with the current stroke color, using `width' as the the
        line width. */
        CGContextStrokeRectWithWidth(context, whiteRect, 2.0) // Fill rectangle using the context data
        
        // ************************************************************
        // Create a red circle in the center
        let circleRect = CGRectInset(whiteRect, 30, 30) // Inset will reduce the size of 'rect' by 30 in x and 30 in y.
        CGContextSetFillColorWithColor(context, UIColor.redColor().CGColor) // Inset will reduce the size of 'rect' by 30 in x and 30 in y.
        CGContextFillEllipseInRect(context, circleRect) // Fill an ellipse confined by the edges of the rect (Note that in this case, since the width and breadth of the rect is same, the resulting ellipse is a circle.)

    }

2. Drawing a Triangle using Bezier Path

Requirement:

CGExample2

Code:

    override func drawRect(rect: CGRect) {
        // Drawing a triangle using UIBezierPath
        let context = UIGraphicsGetCurrentContext()
        
        // Paint the View Blue before drawing the traingle
        CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)  // Set fill color
        CGContextFillRect(context, rect) // Fill rectangle using the context data
                
        // Imagine a triangle resting on the bottom of the container with the base as the width of the rectangle, and the apex of the traingle at the top center of the container
        // The co-ordinates of the rectangle will look like
        // Top = (x: half of Container Width, y: 0 - origin)
        // Bottom Left = (x: 0, y: Container Height)
        // Bottom Right = (x: Container Width, y: Container Height)

        // Create path for drawing a triangle
        var trianglePath = UIBezierPath()
        // First move to the Top point
        trianglePath.moveToPoint(CGPoint(x: self.bounds.width/2, y: 0.0))
        // Add line to Bottom Right
        trianglePath.addLineToPoint(CGPoint(x: self.bounds.width, y: self.bounds.height))
        // Add line to Bottom Left
        trianglePath.addLineToPoint(CGPoint(x: 0.0, y: self.bounds.height))
        // Complete path by drawing path to the Top
        trianglePath.addLineToPoint(CGPoint(x: self.bounds.width/2, y: 0.0))
        
        // Set the fill color
        CGContextSetFillColorWithColor(context, UIColor.greenColor().CGColor)
        // Fill the triangle path
        trianglePath.fill()
    }

3. Drawing an Arc/Semi-Circle using Bezier Path

Requirement:

CGExample3

Code:

    override func drawRect(rect: CGRect) {
        // Drawing an Arc/Semi-Circle using UIBezierPath
        let context = UIGraphicsGetCurrentContext()
    
        // Paint the View Blue before drawing the Semi-Circle
        CGContextSetFillColorWithColor(context, UIColor.blueColor().CGColor)  // Set fill color
        CGContextFillRect(context, rect) // Fill rectangle using the context data
        
        // Imagine the setting Sun. Please go a bit further in your imagination so that the Sun looks like a perfect circle which is perfectly sliced in the middle by the perfectly horizontal horizon. 🙂
        // This can be drawn by having two parameters
        // Bottom Left = (x: 0, y: Container Height)
        // Bottom Right = (x: Container Width, y: Container Height)
        
        // Create path for drawing a triangle
        var semiCirclePath = UIBezierPath()
        // Add Semi-Circle Arc from Bottom Left to Bottom Right
        semiCirclePath.addArcWithCenter(CGPointMake(self.bounds.width / 2, self.bounds.height), radius: self.bounds.width / 2, startAngle: CGFloat(M_PI), endAngle: 0, clockwise: true)
        // Notice that the angle is measured counter-clockwise from right. 
        // You might think that since the path is not complete, this might not work.
        // But the fill method does this for you 🙂
        // Set the fill color
        CGContextSetFillColorWithColor(context, UIColor.greenColor().CGColor)
        // Fill the triangle path
        semiCirclePath.fill()
    }

The fill method used above implicitly closes any open paths before filling it.
CGExample3.1

If you have any questions, do leave it in the comments section below. Thanks for reading!

And one more thing. Ever heard of IBDesignable? No? It makes your life a lot easier when you are drawing. Read more about it here: iOS Tip: Using IBDesignable for previewing custom views

Interesting Reads

References

Related Books

Advertisements

2 thoughts on “Drawing using Core Graphics [Swift]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s