------------------- | | | /\ | | / \ | | /____\ | | | | | | |____| | | | | [Draw Shape] | -------------------
Custom shapes and paths in iOS Swift - Mini App: Build & Ship
import UIKit class CustomShapeView: UIView { // TODO: Override draw(_:) to draw the custom house shape } class ViewController: UIViewController { let shapeView = CustomShapeView() let drawButton = UIButton(type: .system) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white shapeView.translatesAutoresizingMaskIntoConstraints = false shapeView.backgroundColor = .white view.addSubview(shapeView) drawButton.setTitle("Draw Shape", for: .normal) drawButton.translatesAutoresizingMaskIntoConstraints = false view.addSubview(drawButton) NSLayoutConstraint.activate([ shapeView.centerXAnchor.constraint(equalTo: view.centerXAnchor), shapeView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40), shapeView.widthAnchor.constraint(equalToConstant: 200), shapeView.heightAnchor.constraint(equalToConstant: 200), drawButton.topAnchor.constraint(equalTo: shapeView.bottomAnchor, constant: 20), drawButton.centerXAnchor.constraint(equalTo: view.centerXAnchor) ]) // TODO: Add target action for drawButton }
import UIKit class CustomShapeView: UIView { var shouldDrawShape = false override func draw(_ rect: CGRect) { super.draw(rect) guard shouldDrawShape else { return } let width = rect.width let height = rect.height // Draw square base let baseRect = CGRect(x: width * 0.2, y: height * 0.5, width: width * 0.6, height: height * 0.4) let basePath = UIBezierPath(rect: baseRect) UIColor.lightGray.setFill() basePath.fill() UIColor.black.setStroke() basePath.lineWidth = 2 basePath.stroke() // Draw triangular roof let roofPath = UIBezierPath() roofPath.move(to: CGPoint(x: width * 0.2, y: height * 0.5)) roofPath.addLine(to: CGPoint(x: width * 0.5, y: height * 0.2)) roofPath.addLine(to: CGPoint(x: width * 0.8, y: height * 0.5)) roofPath.close() UIColor.lightGray.setFill() roofPath.fill() UIColor.black.setStroke() roofPath.lineWidth = 2 roofPath.stroke() } } class ViewController: UIViewController { let shapeView = CustomShapeView() let drawButton = UIButton(type: .system) override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white shapeView.translatesAutoresizingMaskIntoConstraints = false shapeView.backgroundColor = .white view.addSubview(shapeView) drawButton.setTitle("Draw Shape", for: .normal) drawButton.translatesAutoresizingMaskIntoConstraints = false view.addSubview(drawButton) NSLayoutConstraint.activate([ shapeView.centerXAnchor.constraint(equalTo: view.centerXAnchor), shapeView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 40), shapeView.widthAnchor.constraint(equalToConstant: 200), shapeView.heightAnchor.constraint(equalToConstant: 200), drawButton.topAnchor.constraint(equalTo: shapeView.bottomAnchor, constant: 20), drawButton.centerXAnchor.constraint(equalTo: view.centerXAnchor) ]) drawButton.addTarget(self, action: #selector(drawShape), for: .touchUpInside) } @objc func drawShape() { shapeView.shouldDrawShape = true shapeView.setNeedsDisplay() } }
We created a CustomShapeView subclass of UIView that overrides the draw(_:) method. It draws a house shape using UIBezierPath: a square base and a triangular roof. We use simple colors: light gray fill and black stroke for clear visibility.
We added a Boolean shouldDrawShape to control when to draw the shape. Initially, the shape is not drawn.
In the ViewController, we set up the layout with Auto Layout constraints. The button labeled "Draw Shape" triggers the drawing by setting shouldDrawShape to true and calling setNeedsDisplay() to refresh the view.
This approach keeps drawing logic inside the custom view and user interaction in the controller, which is a clean separation of concerns.
------------------- | | | /\ | | / \ | | /____\ | | | | | | |____| | | | | [Draw Shape] | -------------------
Practice
path(in: CGRect) method in a SwiftUI Shape?Solution
Step 1: Understand the role of
This method is required by thepath(in: CGRect)Shapeprotocol to define the shape's outline.Step 2: Recognize what the method returns
It returns aPaththat describes the shape's drawing instructions inside the given rectangle.Final Answer:
To define the outline of the custom shape by drawing paths. -> Option DQuick Check:
path(in: CGRect)defines shape outline [OK]
path(in:) draws the shape outline [OK]- Confusing path method with color or animation methods
- Thinking it handles user interaction
- Assuming it sets layout constraints
Solution
Step 1: Recall the correct method to move the path cursor
ThePathtype usesmove(to:)to set the starting point.Step 2: Verify method names
Onlymove(to:)is a valid method; others do not exist in SwiftUI'sPath.Final Answer:
path.move(to: CGPoint(x: 50, y: 50)) -> Option BQuick Check:
Usemove(to:)to start path [OK]
move(to:) to set path start point [OK]- Using non-existent methods like start or begin
- Confusing with other graphics frameworks
- Forgetting to use CGPoint for coordinates
struct Triangle: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
path.closeSubpath()
return path
}
}Solution
Step 1: Analyze the points used in the path
The path starts at the top center (midX, minY), then draws lines to bottom left and bottom right corners.Step 2: Understand the shape formed
Connecting these points and closing the path forms a triangle pointing upwards.Final Answer:
A triangle pointing upwards -> Option AQuick Check:
Points form upward triangle [OK]
- Mixing up coordinate system orientation
- Assuming shape is a rectangle or circle
- Ignoring the order of points
struct LineShape: Shape {
func path(in rect: CGRect) -> Path {
var path = Path()
path.move(to: CGPoint(x: 0, y: 0))
path.addLine(to: CGPoint(x: rect.width, y: 0))
return path
}
}Solution
Step 1: Check the path commands
The path moves to (0,0) and draws a line to (rect.width, 0), which is valid inside the rect.Step 2: Verify if closing the path is necessary
Closing a path is optional; lines can be open shapes without errors.Final Answer:
No error; the code correctly draws a horizontal line. -> Option AQuick Check:
Open paths are valid; no error here [OK]
- Assuming closeSubpath() is mandatory
- Confusing rect.width with rect.maxX (both valid here)
- Thinking (0,0) is outside rect bounds
Solution
Step 1: Understand star shape construction
A star is made by connecting points with straight lines in a specific order.Step 2: Identify correct path methods
Starting withmove(to:), then connecting points withaddLine(to:), and closing the path withcloseSubpath()correctly draws the star.Step 3: Evaluate other options
Using ellipses or rectangles won't form a star shape; quad curves create smooth curves, not sharp star points.Final Answer:
Usemove(to:)to start at one point, then useaddLine(to:)to connect all star points in order, finally callcloseSubpath(). -> Option CQuick Check:
Star = move + addLine + closeSubpath [OK]
- Trying to use curves instead of lines for star points
- Using shapes like ellipses or rectangles incorrectly
- Forgetting to close the path to complete the shape
Ready to prove it?
Pass the test and this pattern moves unknown
