#43 Fun with flags in swift (中華民國國旗)
Draw with code
In today's article, we are going to explore how to use UIBezierPath to draw shapes. In fact, we are going to draw our national flags procedurally! Enough chit-chat, let’s dive into it.
Step 1
I’ve created a triangle generator that allows users to control the angle, center, scale, and radius of the star. With this generator, we can call and generate triangles anywhere we want and position them as desired.
You might be wondering, why bother, Jimmy? Why not just manually type in the CGPoint position like everybody else does? Okay, I admit, I’m a bit too obsessed with making everything procedural. But this function truly saves me tons of time by automating repetitive tasks!
func triGenerator(_ size:Double, _ radius:Double ,_ angle:Double, _ centerX:Double, _ centerY:Double )->Void{
// Draw shape
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: 100, y: 0))
path.addLine(to: CGPoint(x: 50, y: 200))
path.close()
//Get center
let centerX = centerX
let centerY = centerY
//Initialize shape
var transform = CGAffineTransform()
//Apply scale
let scale = size
transform = CGAffineTransform(scaleX: scale, y: scale)
path.apply(transform)
//Apply translation to path
transform = CGAffineTransform(translationX: path.bounds.midX * -1, y: radius).concatenating(CGAffineTransform(rotationAngle: CGFloat(-angle * CGFloat.pi / 180)))
.concatenating(CGAffineTransform(translationX: centerX, y: centerY))
path.apply(transform)
//Assign path to layer and render layer to view
let triangleLayer = CAShapeLayer()
triangleLayer.path = path.cgPath
triangleLayer.fillColor = .init(red: 1, green: 1, blue: 1, alpha: 1)
view.layer.addSublayer(triangleLayer)
}
Step 2
Now, let’s create the background views. We’ve got a total of three background views in the national flag if we don’t count that 12-pointed star shape. So, let’s create them one by one, shall we?
The biggest challenge here is to keep everything connected. You see, the national flag has a ratio of 3:2, and we need to move the flag to the center of our screen.
Red Background (backgroundView):
The width of the view is equal to the screen width (
screenWidth
) multiplied by 1.The height of the view is equal to the screen width (
screenWidth
) multiplied by 2/3.Blue Background (blueView):
The width of the view is equal to the screen width (
screenWidth
) multiplied by 0.5.The height of the view is equal to the screen width (
screenWidth
) multiplied by 2/3 and then multiplied by 0.5.
There’s more stuff like this, and it’s easy to lose track of the relationships between values. So my advice is to add comments as much as you can and name variables logically. That’ll keep everything tidy and easy to read.
//Declare screen constant
let screenSize: CGRect = UIScreen.main.bounds
let screenWidth = screenSize.width
let screenHeight = screenSize.height
let centerWidth = screenWidth/2
let centerHeight = screenHeight/2
//Red background
let rect = CGRect(x: 0, y: 0, width: screenWidth * 1, height: screenWidth * 2/3)
let backgroundView = UIView(frame: rect)
let bbWidth = backgroundView.bounds.midX
let bbHeight = backgroundView.bounds.midY
//Blue background
let rect02 = CGRect(x: 0, y: 0.5 * (screenHeight - (screenWidth * 2/3)), width: screenWidth * 0.5, height: screenWidth * 2/3 * 0.5)
let blueView = UIView(frame: rect02)
//Circle view
let rectScale = 25
let rect03 = CGRect(x: -rectScale, y: -rectScale, width: rectScale*2 ,height: rectScale*2)
let circleView = UIView(frame: rect03)
//Adjust blue background
blueView.backgroundColor = UIColor(red: 0, green: 0, blue: 155/255, alpha: 1)
blueView.layer.cornerRadius = 5
//Adjust circle background
circleView.backgroundColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
circleView.layer.cornerRadius = Double(rectScale)
circleView.transform = CGAffineTransform(translationX: screenWidth * 1/4, y: (0.5 * screenHeight - (screenWidth * 2/3 * 0.25)))
//Adjust red background
backgroundView.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
backgroundView.layer.cornerRadius = 15
backgroundView.transform = CGAffineTransform(translationX: -bbWidth + centerWidth, y: -bbHeight + centerHeight)
//Add to subview
view.addSubview(backgroundView)
view.addSubview(blueView)
view.addSubview(circleView)
Step 3
At this final step, we are now ready to add the symbolic 12-pointed star by using the triangle generator we made before. Phew~ finally!
//Generate triangle layer, by using pre-defined function
for i in 0...12{
let angle:Double = 360/12 * Double(i)
//This triangle generator can control scale, radius, angle and center of the star
triGenerator(0.1, 28.5 ,angle, screenWidth/4, (0.5 * screenHeight - (screenWidth * 2/3 * 0.25)))
}
Conclusion/git
It’s important to remember that not everything in your app needs to be automated and procedural. There are times when you simply don’t have the luxury of optimizing everything, especially at work. It’s the harsh reality we face. Sometimes, we have to accept that and focus on other priorities. We can always revisit and dwell on it when we have the time and space at home.