A small iOS app for learning 2D graphics on Apple platforms by example. It pairs
a gallery of focused CoreGraphics (Quartz 2D) demos and a hands-on drawing
screen — all rendered into a real CGContext — with a minimal Metal shader
running on the GPU.
Built with SwiftUI + UIKit interop, targeting iOS.
A practical screen that ties the concepts together:
- Pencil brush — freehand drawing with one finger via
touchesBegan/Moved/Ended, strokes stroked with round line caps/joins for a smooth line. - Pinch to zoom & two-finger rotate — applied to the view's
transformthroughUIPinchGestureRecognizer+UIRotationGestureRecognizer(recognized simultaneously). - Clear — resets strokes and the transform.
Two ideas worth highlighting from the implementation:
- Strokes are stored in the view's local coordinates, which are independent
of
transform, so the drawing stays consistent under any zoom/rotation — no need to invert the transform for touch points. - Single-finger drawing and two-finger gestures don't conflict: when a second
finger appears, the recognizer takes over and the in-progress stroke is
dropped via
touchesCancelled.
Each demo draws into a CGContext and isolates one topic:
- Path: fill & stroke — the core cycle (build path → set color → fill/stroke) and the painter's model.
- Bezier curves — cubic/quadratic curves and their control points.
- Gradients — linear and radial gradients inside a clip region.
- Transforms (CTM) —
translate/rotate/scaleof the coordinate system. - Graphics state + shadows —
saveGState/restoreGState,setShadow. - Blend modes —
multiply,screen. - Text —
NSAttributedString, outlined glyphs. - Offscreen → UIImage — rendering into a bitmap context via
UIGraphicsImageRenderer.
Plus a SwiftUI Canvas example, showing that GraphicsContext is a
declarative facade over the same engine.
A minimal "Hello, GPU" screen: a full-screen fragment shader that animates a plasma pattern, redrawn every frame by the GPU. It shows the whole render pipeline end to end:
MTLDevice→ command queue → render pipeline state (compiled vertex + fragment shaders).- An
MTKViewdrivesdraw(in:)~60 times per second. - Each frame encodes a full-screen triangle plus the current time and presents it.
The point of contrast with CoreGraphics: the fragment shader is written for a single pixel, and the GPU runs it for every pixel in parallel — where CoreGraphics rasterizes on the CPU.
| File | Responsibility |
|---|---|
CGCanvasView.swift |
UIViewRepresentable bridge that exposes a raw CGContext to SwiftUI. |
CGDemos.swift |
The eight CGContext drawing demos. |
DrawingScreen.swift |
The interactive drawing screen (pencil, zoom, rotate, clear). |
Shaders.metal |
Metal Shading Language: the full-screen vertex + plasma fragment shaders. |
MetalScreen.swift |
The Metal renderer, MTKView bridge, and GPU screen. |
ContentView.swift |
Navigation and the SwiftUI Canvas example. |
Inside UIView.draw(_:) the origin is top-left with the Y axis pointing
down (UIKit convention). In a "raw" CoreGraphics context (PDF, bitmap) the
origin is bottom-left, Y up. This matters most when drawing text and images.
Open CGTestApp.xcodeproj in Xcode and run on an iOS Simulator or device.
In the Simulator, pinch/rotate are emulated by holding ⌥ Option (two fingers); add ⇧ Shift to move the finger pair.
Building the Metal shader requires the Metal Toolchain. On Xcode 16+ it is a separate downloadable component; if the build complains it's missing, run:
xcodebuild -downloadComponent MetalToolchain
