Bird
Raised Fist0
iOS Swiftmobile~20 mins

Custom shapes and paths in iOS Swift - Mini App: Build & Ship

Choose your learning style10 modes available
Build: CustomShapeView
This screen shows a custom drawn shape using paths. The shape is a simple house with a square base and a triangular roof.
Target UI
-------------------
|                 |
|       /\        |
|      /  \       |
|     /____\      |
|     |    |      |
|     |____|      |
|                 |
|  [Draw Shape]   |
-------------------
Create a UIView subclass that draws a house shape using UIBezierPath.
The house should have a square base and a triangular roof.
Add a button labeled 'Draw Shape' below the drawing area.
When the button is tapped, the custom shape should appear in the view.
Use clear and simple colors: black stroke and light gray fill.
Ensure the shape is centered and sized nicely within the view.
Starter Code
iOS Swift
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
    }
Task 1
Task 2
Task 3
Solution
iOS Swift
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.

Final Result
Completed Screen
-------------------
|                 |
|       /\        |
|      /  \       |
|     /____\      |
|     |    |      |
|     |____|      |
|                 |
|  [Draw Shape]   |
-------------------
When the user taps the 'Draw Shape' button, the house shape appears inside the top box area.
The shape is centered and sized nicely with a black outline and light gray fill.
Tapping the button multiple times does not redraw or change the shape.
Stretch Goal
Add a 'Clear' button next to 'Draw Shape' that removes the shape from the view.
💡 Hint
Add another UIButton and an action that sets shouldDrawShape to false and calls setNeedsDisplay() on the shapeView.

Practice

(1/5)
1. What is the main purpose of the path(in: CGRect) method in a SwiftUI Shape?
easy
A. To animate the shape's size changes.
B. To set the background color of the shape.
C. To handle user tap gestures on the shape.
D. To define the outline of the custom shape by drawing paths.

Solution

  1. Step 1: Understand the role of path(in: CGRect)

    This method is required by the Shape protocol to define the shape's outline.
  2. Step 2: Recognize what the method returns

    It returns a Path that describes the shape's drawing instructions inside the given rectangle.
  3. Final Answer:

    To define the outline of the custom shape by drawing paths. -> Option D
  4. Quick Check:

    path(in: CGRect) defines shape outline [OK]
Hint: Remember: path(in:) draws the shape outline [OK]
Common Mistakes:
  • Confusing path method with color or animation methods
  • Thinking it handles user interaction
  • Assuming it sets layout constraints
2. Which of the following is the correct way to start drawing a path at a specific point in SwiftUI?
easy
A. path.begin(at: CGPoint(x: 50, y: 50))
B. path.move(to: CGPoint(x: 50, y: 50))
C. path.start(at: CGPoint(x: 50, y: 50))
D. path.setStartPoint(CGPoint(x: 50, y: 50))

Solution

  1. Step 1: Recall the correct method to move the path cursor

    The Path type uses move(to:) to set the starting point.
  2. Step 2: Verify method names

    Only move(to:) is a valid method; others do not exist in SwiftUI's Path.
  3. Final Answer:

    path.move(to: CGPoint(x: 50, y: 50)) -> Option B
  4. Quick Check:

    Use move(to:) to start path [OK]
Hint: Use move(to:) to set path start point [OK]
Common Mistakes:
  • Using non-existent methods like start or begin
  • Confusing with other graphics frameworks
  • Forgetting to use CGPoint for coordinates
3. Given the following SwiftUI shape code, what shape will be drawn?
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
  }
}
medium
A. A triangle pointing upwards
B. A rectangle
C. A circle
D. A triangle pointing downwards

Solution

  1. 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.
  2. Step 2: Understand the shape formed

    Connecting these points and closing the path forms a triangle pointing upwards.
  3. Final Answer:

    A triangle pointing upwards -> Option A
  4. Quick Check:

    Points form upward triangle [OK]
Hint: Visualize points: top center then bottom corners [OK]
Common Mistakes:
  • Mixing up coordinate system orientation
  • Assuming shape is a rectangle or circle
  • Ignoring the order of points
4. Identify the error in this SwiftUI custom shape code:
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
  }
}
medium
A. No error; the code correctly draws a horizontal line.
B. The path does not close the subpath, causing a runtime error.
C. The starting point CGPoint(x: 0, y: 0) is outside the given rect.
D. Using rect.width instead of rect.maxX causes incorrect drawing.

Solution

  1. 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.
  2. Step 2: Verify if closing the path is necessary

    Closing a path is optional; lines can be open shapes without errors.
  3. Final Answer:

    No error; the code correctly draws a horizontal line. -> Option A
  4. Quick Check:

    Open paths are valid; no error here [OK]
Hint: Open paths don't require closing to be valid [OK]
Common Mistakes:
  • Assuming closeSubpath() is mandatory
  • Confusing rect.width with rect.maxX (both valid here)
  • Thinking (0,0) is outside rect bounds
5. You want to create a custom SwiftUI shape that draws a star with 5 points. Which approach correctly combines path methods to achieve this?
hard
A. Use addRect() five times to create star arms.
B. Use addEllipse(in:) repeatedly to draw circles at star points.
C. Use move(to:) to start at one point, then use addLine(to:) to connect all star points in order, finally call closeSubpath().
D. Use move(to:) once and then addQuadCurve(to:control:) to draw smooth star edges without lines.

Solution

  1. Step 1: Understand star shape construction

    A star is made by connecting points with straight lines in a specific order.
  2. Step 2: Identify correct path methods

    Starting with move(to:), then connecting points with addLine(to:), and closing the path with closeSubpath() correctly draws the star.
  3. Step 3: Evaluate other options

    Using ellipses or rectangles won't form a star shape; quad curves create smooth curves, not sharp star points.
  4. Final Answer:

    Use move(to:) to start at one point, then use addLine(to:) to connect all star points in order, finally call closeSubpath(). -> Option C
  5. Quick Check:

    Star = move + addLine + closeSubpath [OK]
Hint: Connect star points with lines, then close path [OK]
Common Mistakes:
  • 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
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard