Clipping Paths

Episode #285 | 6 minutes | published on July 14, 2017 | Uses swift-3.0, Xcode-8.3
Clipping paths allow you to control where drawing occurs by providing a path in which to limit future drawing operations. Since you can create arbitrary shapes with CGMutablePath (and UIBezierPath) this can be an essential technique to get the drawing effect in some situations.

Links

Drawing inside a Clipping Path

// create path
let path = CGMutablePath()
path.addEllipse(in: CGRect(x: 25, y: 25, width: 100, height: 100))

// clip to the path
context.addPath(path)
context.clip()

// draw inside the clipped area
context.setFillColor(CGColor(colorSpace: colorSpace, components: [1, 0, 0, 1])!)
context.fill(CGRect(x: 50, y: 50, width: 100, height: 100))

Notice that the rect we filled will only be drawn inside the circle path we defined earlier.

Undoing a Clip

It isn't really possible to undo a clipping operation (other than resetting the clip to be the entire drawing rect), so if you want to add more drawing outside a previously clipped path, you'll have to rely on the graphics state stack:


// save the current graphics state
context.saveGState()

// create path
let path = CGMutablePath()
path.addEllipse(in: CGRect(x: 25, y: 25, width: 100, height: 100))

// clip to the path
context.addPath(path)
context.clip()

// draw inside the clipped area
context.setFillColor(CGColor(colorSpace: colorSpace, components: [1, 0, 0, 1])!)
context.fill(CGRect(x: 50, y: 50, width: 100, height: 100))

// 'pop' the state off the stack, undoing any previous drawing operations
context.restoreGState()

// draw more stuff, which won't be clipped
context.setFillColor(CGColor(colorSpace: colorSpace, components: [0, 0, 1, 1])!)
context.fill(CGRect(x: 150, y: 150, width: 100, height: 100))

Drawing a Gradient Inside a Rounded Rect

We can do more complex drawings as well and clip them to get interesting effects. This might be used as the basis for an iOS-style icon:

let path = CGMutablePath()
path.addRoundedRect(in: CGRect(x: 50, y: 50, width: 100, height: 100), cornerWidth: 10, cornerHeight: 10)
    context.addPath(path)
    context.clip()

let colors = [
    CGColor(colorSpace: colorSpace, components: [1, 0, 0, 1])!,
    CGColor(colorSpace: colorSpace, components: [1, 0, 1, 1])!
] as CFArray

let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: nil)!

context.drawLinearGradient(gradient, 
    start: CGPoint(x: 50, y: 50),
    end: CGPoint(x: 50, y: 150),
    options: [.drawsAfterEndLocation, .drawsBeforeStartLocation])
blog comments powered by Disqus