Bird
Raised Fist0
iOS Swiftmobile~20 mins

Canvas for drawing in iOS Swift - Mini App: Build & Ship

Choose your learning style10 modes available
Build: Drawing Canvas
A screen where users can draw freehand lines with their finger on a blank canvas.
Target UI
-------------------------
| Drawing Canvas        |
|-----------------------|
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|-----------------------|
| Clear Button          |
-------------------------
A full screen white canvas area where user can draw lines with finger.
Lines should follow the finger movement smoothly.
A Clear button at the bottom to erase the drawing and start fresh.
Use UIKit and Swift.
Support portrait orientation.
Starter Code
iOS Swift
import UIKit

class DrawingView: UIView {
    // TODO: Add properties to track lines and touch points

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // TODO: Start a new line
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        // TODO: Add points to current line and redraw
    }

    override func draw(_ rect: CGRect) {
        // TODO: Draw all lines
    }

    func clear() {
        // TODO: Clear all lines and redraw
    }
}

class DrawingViewController: UIViewController {
    let drawingView = DrawingView()
    let clearButton = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        drawingView.frame = view.bounds
        drawingView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(drawingView)

        clearButton.setTitle("Clear", for: .normal)
        clearButton.frame = CGRect(x: 20, y: view.bounds.height - 60, width: 100, height: 40)
        clearButton.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin]
        view.addSubview(clearButton)

        // TODO: Add target action for clear button
    }
Task 1
Task 2
Task 3
Task 4
Solution
iOS Swift
import UIKit

class DrawingView: UIView {
    private var lines: [[CGPoint]] = []

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let point = touches.first?.location(in: self) else { return }
        lines.append([point])
        setNeedsDisplay()
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let point = touches.first?.location(in: self), !lines.isEmpty else { return }
        lines[lines.count - 1].append(point)
        setNeedsDisplay()
    }

    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        context.setStrokeColor(UIColor.black.cgColor)
        context.setLineWidth(2)
        context.setLineCap(.round)

        for line in lines {
            guard let firstPoint = line.first else { continue }
            context.beginPath()
            context.move(to: firstPoint)
            for point in line.dropFirst() {
                context.addLine(to: point)
            }
            context.strokePath()
        }
    }

    func clear() {
        lines.removeAll()
        setNeedsDisplay()
    }
}

class DrawingViewController: UIViewController {
    let drawingView = DrawingView()
    let clearButton = UIButton(type: .system)

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white

        drawingView.frame = view.bounds
        drawingView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(drawingView)

        clearButton.setTitle("Clear", for: .normal)
        clearButton.frame = CGRect(x: 20, y: view.bounds.height - 60, width: 100, height: 40)
        clearButton.autoresizingMask = [.flexibleTopMargin, .flexibleRightMargin]
        view.addSubview(clearButton)

        clearButton.addTarget(self, action: #selector(clearDrawing), for: .touchUpInside)
    }

    @objc func clearDrawing() {
        drawingView.clear()
    }
}

We created a DrawingView subclass of UIView to handle touch events and drawing. It keeps an array of lines, where each line is an array of points. When the user touches the screen, a new line starts. As the finger moves, points are added to the current line. The draw(_:) method draws all lines with smooth black strokes.

The clear() method empties all lines and refreshes the view.

The DrawingViewController sets up the drawing view and a clear button. The button calls clear() on the drawing view to erase the canvas.

This approach uses UIKit touch handling and Core Graphics drawing to create a simple finger drawing canvas.

Final Result
Completed Screen
-------------------------
| Drawing Canvas        |
|-----------------------|
|                       |
|  *  *                 |
|   *   *               |
|    *    *             |
|     *     *           |
|      *      *         |
|       *       *       |
|        *        *     |
|         *         *   |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|                       |
|-----------------------|
| Clear Button          |
-------------------------
User draws lines by dragging finger on the white canvas area.
Lines appear following finger movement smoothly.
Tapping Clear button erases all lines and resets the canvas.
Stretch Goal
Add an Undo button to remove the last drawn line.
💡 Hint
Keep track of lines in an array and remove the last one when Undo is tapped, then call setNeedsDisplay() to refresh.

Practice

(1/5)
1. What is the primary method to override when drawing custom shapes on a UIView in Swift?
easy
A. draw(_ rect: CGRect)
B. viewDidLoad()
C. layoutSubviews()
D. updateConstraints()

Solution

  1. Step 1: Understand UIView drawing lifecycle

    Custom drawing on a UIView is done by overriding the draw(_:) method.
  2. Step 2: Identify the correct method signature

    The method draw(_ rect: CGRect) provides the area to draw and is called automatically when the view needs to render.
  3. Final Answer:

    draw(_ rect: CGRect) -> Option A
  4. Quick Check:

    Custom drawing uses draw(_:) [OK]
Hint: Remember: draw(_:) is the canvas entry point [OK]
Common Mistakes:
  • Confusing draw(_:) with viewDidLoad()
  • Trying to draw in layoutSubviews()
  • Using updateConstraints() for drawing
2. Which Swift code correctly obtains the current graphics context inside draw(_:) for drawing?
easy
A. let context = UIView.getContext()
B. let context = CGContext.current()
C. let context = GraphicsContext.shared
D. let context = UIGraphicsGetCurrentContext()

Solution

  1. Step 1: Recall the UIKit method for graphics context

    UIGraphicsGetCurrentContext() is the correct UIKit function to get the current drawing context.
  2. Step 2: Verify method names and usage

    Other options are not valid UIKit methods or properties for getting the context.
  3. Final Answer:

    let context = UIGraphicsGetCurrentContext() -> Option D
  4. Quick Check:

    Use UIGraphicsGetCurrentContext() to get context [OK]
Hint: Use UIGraphicsGetCurrentContext() inside draw(_:) [OK]
Common Mistakes:
  • Using non-existent methods like CGContext.current()
  • Trying to get context outside draw(_:) method
  • Confusing UIView methods with graphics context
3. What will be the visible result of this draw(_:) code snippet?
override func draw(_ rect: CGRect) {
  guard let ctx = UIGraphicsGetCurrentContext() else { return }
  ctx.setFillColor(UIColor.red.cgColor)
  ctx.fill(CGRect(x: 10, y: 10, width: 100, height: 50))
}
medium
A. A blue rectangle at position (10,10) sized 100x50
B. A red rectangle at position (10,10) sized 100x50
C. No visible shape because fill color is not set
D. A red circle at position (10,10) sized 100x50

Solution

  1. Step 1: Analyze fill color and shape

    The code sets fill color to red and fills a rectangle at (10,10) with width 100 and height 50.
  2. Step 2: Confirm shape and color match

    The shape is a rectangle, not a circle, and color is red, not blue.
  3. Final Answer:

    A red rectangle at position (10,10) sized 100x50 -> Option B
  4. Quick Check:

    Fill color red + fill rect = red rectangle [OK]
Hint: Fill color sets shape color; fill draws the shape [OK]
Common Mistakes:
  • Assuming default color is blue
  • Confusing rectangle with circle
  • Forgetting to unwrap context safely
4. Identify the error in this draw(_:) method that prevents drawing a blue circle:
override func draw(_ rect: CGRect) {
  let ctx = UIGraphicsGetCurrentContext()
  ctx.setFillColor(UIColor.blue.cgColor)
  ctx.fillEllipse(in: CGRect(x: 20, y: 20, width: 80, height: 80))
}
medium
A. UIColor.blue.cgColor is invalid
B. Incorrect method fillEllipse(in:) used
C. Missing guard or forced unwrap for context
D. CGRect coordinates are out of bounds

Solution

  1. Step 1: Check context unwrapping

    The context is optional and not safely unwrapped, causing a compile error because ctx is optional.
  2. Step 2: Confirm method and color correctness

    fillEllipse(in:) and UIColor.blue.cgColor are correct and CGRect is valid.
  3. Final Answer:

    Missing guard or forced unwrap for context -> Option C
  4. Quick Check:

    Always unwrap context safely before drawing [OK]
Hint: Always unwrap UIGraphicsGetCurrentContext() safely [OK]
Common Mistakes:
  • Ignoring optional context leading to no drawing
  • Thinking fillEllipse(in:) is wrong method
  • Assuming color cgColor is invalid
5. You want to draw a green rectangle with a black border of 5 points thickness inside draw(_:). Which code snippet correctly achieves this?
hard
A. ctx.setFillColor(UIColor.green.cgColor) ctx.fill(rect) ctx.setStrokeColor(UIColor.black.cgColor) ctx.setLineWidth(5) ctx.stroke(rect)
B. ctx.setStrokeColor(UIColor.green.cgColor) ctx.stroke(rect) ctx.setFillColor(UIColor.black.cgColor) ctx.fill(rect) ctx.setLineWidth(5)
C. ctx.setFillColor(UIColor.black.cgColor) ctx.fill(rect) ctx.setStrokeColor(UIColor.green.cgColor) ctx.setLineWidth(5) ctx.stroke(rect)
D. ctx.setLineWidth(5) ctx.setFillColor(UIColor.green.cgColor) ctx.stroke(rect) ctx.setStrokeColor(UIColor.black.cgColor) ctx.fill(rect)

Solution

  1. Step 1: Fill rectangle with green color

    Set fill color to green and fill the rect to paint the inside.
  2. Step 2: Draw black border with stroke

    Set stroke color to black, line width to 5, then stroke the rect to draw border.
  3. Final Answer:

    Fill green then stroke black with line width 5 -> Option A
  4. Quick Check:

    Fill first, then stroke for border [OK]
Hint: Fill shape first, then stroke border with line width [OK]
Common Mistakes:
  • Swapping fill and stroke colors
  • Setting line width after stroke call
  • Calling stroke before fill
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