Day 88 - 100 Days of Swift
Project 27 (part 1)
Day 88 is the first part of the twenty-seventh project. It is a technique project focused on CoreGraphics
where you write a bunch of methods to draw some different things into a UIImageView
. You draw a rectangle and a circle. You draw checkerboard and some overlapping squares rotated around their center. You draw a kind of abstract series of lines that results in a cool spiral graphic sort of thing. And you draw an image and some text into a new image.
First, you get the project set up. You make a single view app, fill up the whole view with an image view and then place a button on top of it down at the bottom of the screen. You add an outlet for the image view and an action for the button. You also add a property called currentDrawType
to help cycle through the different options:
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
var currentDrawType = 0
@IBOutlet weak var imageView: UIImageView!
@IBAction func redraw(_ sender: Any) {
currentDrawType += 1
currentDrawType %= 6
switch currentDrawType {
case 0:
break
case 1:
break
case 2:
break
case 3:
break
case 4:
break
case 5:
break
default:
break
}
}
Then you add the first function which is called drawRectangle
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func drawRectangle() {
let size = CGSize(width: 512,
height: 512)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
let rectangle = CGRect(origin: .zero,
size: size)
.insetBy(dx: 10, dy: 10)
context.cgContext.setStrokeColor(UIColor.black.cgColor)
context.cgContext.setLineWidth(10)
context.cgContext.addRect(rectangle)
context.cgContext.drawPath(using: .stroke)
}
imageView.image = image
}
This makes a UIGraphicsImageRenderer
with the size we gave it. I don’t know much about this class, but it seems like it is basically a simple way to bridge between UIKit
and CoreGraphics
. Then it has that renderer make an image from the closure that we pass. In that closure we set the strokeColor
to black and the lineWidth
to 10, add a rectangle to draw, and then draw the path of that rectangle using the stroke we set up. Finally, it sets the imageView’s image to be the image we produced.
You call this method in viewDidLoad
and set it to be the content of the first case in the switch statement. This results in a rectangle that looks like this:

Next, you draw a circle. The code for this is almost identical, except you use addEllipse(in:)
instead of addRect()
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func drawCircle() {
let size = CGSize(width: 512,
height: 512)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
let rectangle = CGRect(origin: .zero,
size: size)
.insetBy(dx: 10, dy: 10)
context.cgContext.setStrokeColor(UIColor.black.cgColor)
context.cgContext.setLineWidth(10)
context.cgContext.addEllipse(in: rectangle)
context.cgContext.drawPath(using: .stroke)
}
imageView.image = image
}
You add that as the second case and it results in a circle that looks like this:

For the checkerboard you loop through and fill individual rectangles instead of adding them to the context:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func drawCheckerboard() {
let size = CGSize(width: 512,
height: 512)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
context.cgContext.setFillColor(UIColor.black.cgColor)
for row in 0..<8 {
for col in 0..<8 {
if (row + col) % 2 == 0 {
context.cgContext.fill(CGRect(x: col * 64,
y: row * 64,
width: 64,
height: 64))
}
}
}
}
imageView.image = image
}
This is the third case and it results in a checkerboard that looks like this:

For the rotated rectangles you translate the context, so that you’re rotating around the middle instead of the top left corner, then you rotate the context and add a rectangle, rotate it again and add another rectangle, etc. This one is kind of cool because there is a constant for how many rectangles you want to draw and the math will draw them evenly spaced throughout the circle:
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
26
func drawRotatedSquares() {
let size = CGSize(width: 512,
height: 512)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
context.cgContext.translateBy(x: 256, y: 256)
let rotations = 3
let amount = Double.pi / Double(rotations)
for _ in 0 ..< rotations {
context.cgContext.rotate(by: CGFloat(amount))
let rect = CGRect(x: -128,
y: -128,
width: 256,
height: 256)
context.cgContext.addRect(rect)
}
context.cgContext.setStrokeColor(UIColor.black.cgColor)
context.cgContext.strokePath()
}
imageView.image = image
}
You add this as the fourth case and it results in an image that looks like this:

If you change rotations
to be 100, it looks like this:

For the abstract lines you do a similar thing where you rotate the context and then add a line, rotate the context and add a line, etc. Only this time the line shrinks as you go, so it sort of spirals into itself.
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
26
27
28
29
30
31
func drawLines() {
let size = CGSize(width: 512,
height: 512)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
context.cgContext.translateBy(x: 256, y: 256)
var first = true
var length: CGFloat = 256
for _ in 0 ..< 256 {
context.cgContext.rotate(by: .pi / 2)
let point = CGPoint(x: length, y: 50)
if first {
context.cgContext.move(to: point)
first = false
} else {
context.cgContext.addLine(to: point)
}
length *= 0.99
}
context.cgContext.setStrokeColor(UIColor.black.cgColor)
context.cgContext.strokePath()
}
imageView.image = image
}
You add that as the fifth case and it results in an image that looks like this:

For the final function, you set up an attributed string and draw it into a rectangle, and you grab a UIImage
and draw that starting at a given point:
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
26
27
28
29
30
31
32
33
34
35
36
37
func drawImagesAndText() {
let size = CGSize(width: 512,
height: 512)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { context in
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attrs: [NSAttributedString.Key: Any] = [
.font: UIFont.systemFont(ofSize: 36),
.paragraphStyle: paragraphStyle
]
let string = """
The best-laid schemes o'
mice an' men gang aft agley
"""
let attributedString = NSAttributedString(string: string,
attributes: attrs)
let stringRect = CGRect(x: 32,
y: 32,
width: 448,
height: 448)
attributedString.draw(with: stringRect,
options: .usesLineFragmentOrigin,
context: nil)
let mouse = UIImage(named: "mouse")
mouse?.draw(at: CGPoint(x: 300, y: 150))
}
imageView.image = image
}
You add that as the sixth and final case and it results in an image that looks like this:

And that’s it for today! It’s kind of cool the interesting things you can draw with just a little bit of code.
You can find my version of this project at the end of day 88 on Github here.