From Line to gradient graph – CoreGraphics Tutorial in Swift

            CoreGraphics is a powerful framework for drawing two-dimensional shapes. It is available on iOS and also on OS X when it comes to application development. It is based on Quartz 2D API which is a framework written in C language.

CoreGraphics is, in some way, connected to UIKit framework but it is more powerful and it lays on the lower layer within the OS. CG operations do not have to be calculated on the main thread comparing to UIKit and therefore drawing using this framework is much faster and it can be done in real time – effects are really impressive.

CoreGraphics is able to draw bezier paths, patterns, shadows, gradients and set their stroke and fill colors. Each drawing has its own context – an object which has an information about drawing properties such as line width, line color, blend mode, transformation matrix and much more. Further information about configuring graphic context can be found here.

CoreGraphics has an ability to draw each part of a shape with a different property (different context). To achieve this, we are creating new context by pushing it on the context stack. We use CGContextSaveGState() function. In case you want to return to the previous context we pop current context off the stack using CGContextRestoreGState() function. If you want to get the current context we use UIGraphicsGetCurrentContext() function.

When we want to draw a shape using CoreGraphics we usually override UIView’s drawRect(_:) function. In this tutorial, I would like to present a drawing of some basic shapes and then based on that present some complex shapes. I want to focus especially on those complex shapes as basic shape code is quite easy and straightforward and everyone would probably understand what is going on there. Every piece of code is written in Swift and everything is gathered in a playground project so everyone could test it easily. The repository with a project can be found here.

Basic Shapes

Basic sinusWhen creating basic shapes, I used some of the code from here, as I would like to present a use of CoreGraphics API. So what should we exactly do to draw a shape in CoreGraphics? At first, we should get a current graphic context and then set drawing properties within this context, such as stroke / fill color, line width etc. After that we should create a path for the shape (e.g. oval, rect, curve, arc) and finally fill it and stroke it with a color. Below there are some examples of creating simple shapes and a corresponding code snippet responsible for a particular drawing.

 

 

 

 

 

 

 1

public override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 4.0)
CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
let rectangle = CGRectMake(60,170,200,80)
CGContextAddEllipseInRect(context, rectangle)
CGContextStrokePath(context)
}
 2
public override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 20.0)
CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
let dashArray:[CGFloat] = [2,6,4,2]
CGContextSetLineDash(context, 3, dashArray, 4)
CGContextMoveToPoint(context, 10, 200)
CGContextAddQuadCurveToPoint(context,150, 10, 300, 200 )
CGContextStrokePath(context)
}
 3
public override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
CGContextSetLineWidth(context, 2.0)
CGContextSetStrokeColorWithColor(context,
UIColor.blueColor().CGColor)
CGContextMoveToPoint(context, 100, 100)
CGContextAddLineToPoint(context, 150, 150)
CGContextAddLineToPoint(context, 100, 200)
CGContextAddLineToPoint(context, 50, 150)
CGContextAddLineToPoint(context, 100, 100)
CGContextStrokePath(context)
}
 4
public override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let myShadowOffset = CGSizeMake (-10,  15)
CGContextSaveGState(context)
CGContextSetShadow (context, myShadowOffset, 5)
CGContextSetLineWidth(context, 4.0)
CGContextSetStrokeColorWithColor(context, UIColor.blueColor().CGColor)
let rectangle = CGRectMake(60,170,200,80)
CGContextAddEllipseInRect(context, rectangle)
CGContextStrokePath(context)
CGContextRestoreGState(context)
}
 5
public override func drawRect(rect: CGRect) {
let context = UIGraphicsGetCurrentContext()
let locations: [CGFloat] = [ 0.0, 0.25, 0.5, 0.75 ]
let colors = [UIColor.redColor().CGColor,
UIColor.greenColor().CGColor,
UIColor.blueColor().CGColor,
UIColor.yellowColor().CGColor]
let colorspace = CGColorSpaceCreateDeviceRGB()
let gradient = CGGradientCreateWithColors(colorspace, colors, locations)
var startPoint = CGPoint()
var endPoint =  CGPoint()
startPoint.x = 0.0
startPoint.y = 0.0
endPoint.x = 600
endPoint.y = 600
CGContextDrawLinearGradient(context, gradient,
startPoint, endPoint, .DrawsAfterEndLocation)
}

 

Complex Shapes

Dotted Curve with a Shadow

In order to achieve this dotted curve we need to set a shadow using CGContextSetShadow(). The function, apart from current context, takes into account two more parameters: shadowOffset which is an offset from the shape and blur which is an amount of blur to be applied on a shape. Then we need to create a quadratic curve from some points using CGContextAddQuadCurveToPoint() function. At the end we need to set the dashes using CGContextSetLineDash() function and a type of dash to be round using CGContextSetLineCap() function.

  

Gradient Donut

Gradient donutBefore we apply a gradient to our donut-like shape, we try to understand how a core graphics works when it comes to drawing a shape with a hole. In fact, the above shape can be drawn using different methods. I would like to present three of them.

The first method is called Winding Count Fill Rule. It is strictly connected with a way how CoreGraphics cope with drawing shapes with a fill color. The algorithm has a few crucial steps according to the article:

1. CoreGraphics draws every horizontal row within the path’s bounding rectangle from left-to-right
2. At the start of each row, CoreGraphics sets the winding count for the shape to zero.
3. If CoreGraphics crosses a line in the shape at any point during the row, it notes if the line was going upwards or downwards at the point where CoreGraphics crossed it.
4. An upward line increases the winding count of the shape by 1.
5. A downward line decreases the winding count of the shape by 1.
6. If the winding count for the shape is ever non-zero (positive or negative) then pixels are filled according to the color of the shape.

 

To sum this algorithm up, when we draw two circles – one clockwise and the second one counter clockwise with a smaller radius and then fill the shape with a color, we get the desired result as shown in a picture above.

gradient circleThe second method is Even Odd Rule. In even odd, the utmost boundary begins the object, the next uttermost turns it off again, and so on for other nested paths. Here it doesn’t matter in which way we draw a circle (clockwise or counterclockwise).

The third method is Clipping a Region. We can produce our own path and clip it in order to fill only this path with the desired color.

I recommend to try run the code with all of those methods and see the results. When we know how to clip a certain path to make a hole in a shape we can produce a gradient layer to fill our donut. We do exactly the same steps as above, but instead of filling a shape with a color we produce an additional gradient. The result can be seen below.

 

 

Gradient Graph

Gradient Graph ios swift

When we want to create a gradient below a graph we need to set a graph path as a clipping path. First, we should create a graph path from random points and then clip it in order to be able to fill this path with a gradient. We should remember that we have to close the path in order to draw a gradient properly. Lastly, we should draw a graph path using a different context. The code can be found in the repository mentioned above.

 

 

 

 

 

Thanks for reading, and remember if you liked this article please spread the word and forward it to your friends. If you have any questions feel free to shoot me @olbartek.
I’ll be more than happy to answer any of your questions. Cheers