Day 91 - 100 Days of Swift
Consolidation X (part 2)
Day 91 is the second consolidation day centered around Core Graphics. He gives you a playground with a bunch of challenges to draw various things using Core Graphics, so today is going to be a lot of pictures.
I’m assuming this is also used for people who are totally new to Core Graphics
because it starts with a very simple challenge: to draw a square in the center of another square:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Every challenge includes this, so I'll skip it and only show
// the .image closure in the rest of them.
let rect = CGRect(x: 0, y: 0, width: 1000, height: 1000)
let renderer = UIGraphicsImageRenderer(bounds: rect)
let rendered = renderer.image { ctx in
// Draws the original rectangle
UIColor.blue.setFill()
ctx.cgContext.fill(CGRect(x: 200,
y: 200,
width: 600,
height: 600))
// Draws my rectangle
UIColor.red.setFill()
ctx.cgContext.fill(CGRect(x: 400,
y: 400,
width: 200,
height: 200))
}
// I'll also skip this because it is the same.
showOutput(rendered)

The next challenge was to draw five rectangles of equal height and width in five different colors:
1
2
3
4
5
6
7
8
let colors: [UIColor] = [.red, .orange, .yellow, .green, .blue]
let width = Int(rect.width) / colors.count
colors.enumerated().forEach {
let origin = CGPoint(x: width * $0, y: 0)
let size = CGSize(width: width, height: 1000)
$1.setFill()
ctx.cgContext.fill(CGRect(origin: origin, size: size))
}
I chose to write it in such a way that you can add as many colors as you want and it will render correctly. With the colors he gives you, it looks like this:

The next challenge is to draw a “flag” which consists of a centered orange cross on top of a yellow background:
1
2
3
4
5
6
UIColor.yellow.setFill()
ctx.cgContext.fill(rect)
UIColor.orange.setFill()
ctx.cgContext.fill(CGRect(x: 0, y: 400, width: 1000, height: 200))
ctx.cgContext.fill(CGRect(x: 400, y: 0, width: 200, height: 1000))

The next is to draw a checkerboard that is 10 x 10. Again, I chose to make it so that you can set whatever size you want and it will render correctly:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
UIColor.black.setFill()
let numChecks = 10
let size = edge / numChecks
for row in 0 ..< numChecks {
for col in 0 ..< numChecks {
if (row + col) % 2 == 0 {
let origin = CGPoint(x: col * size, y: row * size)
let size = CGSize(width: size, height: size)
ctx.cgContext.fill(CGRect(origin: origin, size: size))
}
}
}

The next challenge is to draw four circles in four different colors. I tried to follow the same pattern of reusability with this one, but I’m not super happy with how the code turned out:
1
2
3
4
5
6
7
8
let colors: [[UIColor]] = [[.red, .blue], [.yellow, .green]]
zip([0,0,1,1], [0,1,0,1]).forEach { x, y in
let origin = CGPoint(x: x*500, y: y*500)
let size = CGSize(width: 500, height: 500)
let color = colors[x][y]
color.setFill()
ctx.cgContext.fillEllipse(in: CGRect(origin: origin, size: size))
}

The next is to draw a “flower” which consists of four overlapping red circles and a smaller black circle in the middle:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
UIColor.red.setFill()
let circles = [
CGRect(x: 100, y: 100, width: 500, height: 500),
CGRect(x: 400, y: 100, width: 500, height: 500),
CGRect(x: 100, y: 400, width: 500, height: 500),
CGRect(x: 400, y: 400, width: 500, height: 500)
]
circles.forEach {
ctx.cgContext.fillEllipse(in: $0)
}
UIColor.black.setFill()
let center = CGRect(x: 400, y: 400, width: 200, height: 200)
ctx.cgContext.fillEllipse(in: center)

Next, you draw a “logo” which consists of four smaller circles that are filled with red and stroked in black that surround a larger circle in the center:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
UIColor.red.setFill()
UIColor.black.setStroke()
ctx.cgContext.setLineWidth(40)
let bigCircle = CGRect(x: 300, y: 300, width: 400, height: 400)
ctx.cgContext.addEllipse(in: bigCircle)
ctx.cgContext.drawPath(using: .fillStroke)
let circles = [
CGRect(x: 400, y: 100, width: 200, height: 200),
CGRect(x: 100, y: 400, width: 200, height: 200),
CGRect(x: 400, y: 700, width: 200, height: 200),
CGRect(x: 700, y: 400, width: 200, height: 200),
]
circles.forEach {
ctx.cgContext.addEllipse(in: $0)
}
ctx.cgContext.setLineWidth(10)
ctx.cgContext.drawPath(using: .fillStroke)

Next, you draw a rainbow using concentric circles:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ctx.cgContext.setLineWidth(50)
let colors: [UIColor] = [
.red,
.orange,
.yellow,
.green,
.blue,
.purple
]
var xPos = 0
var yPos = 500
var size = 1000
for color in colors {
xPos += 50
yPos += 50
size -= 100
let rect = CGRect(x: xPos, y: yPos, width: size, height: size)
color.setStroke()
ctx.cgContext.strokeEllipse(in: rect)
}

Next, you draw an “emoji” that consists of a few smaller circles for eyes and a mouth, over a larger yellow circle as the face:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
UIColor.black.setStroke()
UIColor.yellow.setFill()
ctx.cgContext.setLineWidth(10)
let face = CGRect(x: 100, y: 100, width: 800, height: 800)
ctx.cgContext.addEllipse(in: face)
ctx.cgContext.drawPath(using: .fillStroke)
let eyes = [
CGRect(x: 250, y: 300, width: 150, height: 150),
CGRect(x: 600, y: 300, width: 150, height: 150)
]
eyes.forEach { ctx.cgContext.addEllipse(in: $0) }
UIColor.black.setFill()
ctx.cgContext.drawPath(using: .fillStroke)
let mouth = CGRect(x: 350, y: 500, width: 300, height: 300)
ctx.cgContext.addEllipse(in: mouth)
UIColor.brown.setFill()
ctx.cgContext.drawPath(using: .fillStroke)

Next, you render some text to finish up the catchy little phrase:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let firstPosition = rect.offsetBy(dx: 0, dy: 300)
let firstText = "The early bird catches the worm."
let firstAttrs: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 72),
.foregroundColor: UIColor.blue
]
let firstString = NSAttributedString(string: firstText,
attributes: firstAttrs)
firstString.draw(in: firstPosition)
let secondPosition = rect.offsetBy(dx: 0, dy: 398)
let secondText = "But the second mouse gets the cheese."
let secondAttrs: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 72),
.foregroundColor: UIColor.red
]
let secondString = NSAttributedString(string: secondText,
attributes: secondAttrs)
secondString.draw(in: secondPosition)

Next, you draw a Scottish saltire, which consists of two diagonal white lines over a blue background:
1
2
3
4
5
6
7
8
9
10
11
UIColor(red: 0, green: 0.37, blue: 0.72, alpha: 1).setFill()
ctx.cgContext.fill(rect)
ctx.cgContext.move(to: CGPoint(x: 0, y: 0))
ctx.cgContext.addLine(to: CGPoint(x: 1000, y: 1000))
ctx.cgContext.move(to: CGPoint(x: 1000, y: 0))
ctx.cgContext.addLine(to: CGPoint(x: 0, y: 1000))
UIColor.white.setStroke()
ctx.cgContext.setLineWidth(100)
ctx.cgContext.strokePath()

Next, you draw an image with a black border around it. For this one I added a little extension to make it easier to figure out the origin point for drawing things centered in another frame:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
extension CGSize {
func centerOffset(in rect: CGRect) -> CGPoint {
let x = (rect.width - self.width) / 2 + rect.origin.x
let y = (rect.height - self.height) / 2 + rect.origin.y
return CGPoint(x: x, y: y)
}
}
// In image closure
UIColor.darkGray.setFill()
ctx.cgContext.fill(rect)
let frameEdge = 640
let frameSize = CGSize(width: frameEdge, height: frameEdge)
let frameOrigin = frameSize.centerOffset(in: rect)
UIColor.black.setFill()
let frameRect = CGRect(origin: frameOrigin, size: frameSize)
ctx.cgContext.fill(frameRect)
let frameWidth = 20
let imageEdge = frameEdge - 2 * frameWidth
let imageSize = CGSize(width: imageEdge, height: imageEdge)
let imageOrigin = imageSize.centerOffset(in: frameRect)
let imageRect = CGRect(origin: imageOrigin, size: imageSize)
mascot?.draw(in: imageRect)
This makes it really clean to update either the overall size of the frame or the border around the image without having to figure out all the other math yourself.

Next, you draw a series of circles that stretch across the context by translating the context itself:
1
2
3
4
5
6
7
8
let ellipseRectangle = CGRect(x: 0, y: 300, width: 400, height: 400)
ctx.cgContext.setLineWidth(8)
UIColor.red.setStroke()
for _ in 1...7 {
ctx.cgContext.strokeEllipse(in: ellipseRectangle)
ctx.cgContext.translateBy(x: 100, y: 0)
}

Next, you draw another “logo” which consists of eight squares rotated around the center:
1
2
3
4
5
6
7
8
9
10
11
let boxRectangle = CGRect(x: 0, y: 0, width: 300, height: 300)
ctx.cgContext.setLineWidth(8)
ctx.cgContext.translateBy(x: 500, y: 500)
for _ in 1...8 {
ctx.cgContext.addRect(boxRectangle)
ctx.cgContext.rotate(by: .pi / 4)
}
UIColor.red.setStroke()
ctx.cgContext.strokePath()

Finally, you mess around with blend modes to see how Core Graphics
blends overlapping elements together. My favorite is .xor
:
1
2
3
4
5
6
7
ctx.cgContext.setBlendMode(.xor)
UIColor.red.setFill()
ctx.cgContext.fillEllipse(in: CGRect(x: 200, y: 200, width: 400, height: 400))
ctx.cgContext.fillEllipse(in: CGRect(x: 400, y: 200, width: 400, height: 400))
ctx.cgContext.fillEllipse(in: CGRect(x: 400, y: 400, width: 400, height: 400))
ctx.cgContext.fillEllipse(in: CGRect(x: 200, y: 400, width: 400, height: 400))

You can find my version of this project at the end of day 91 on Github here.