Drawing App
A freehand drawing application using the Canvas view with pan gesture handling. Demonstrates how to draw lines on a canvas, change stroke color and width, and clear the drawing.
Full Source¶
import nib
def main(app: nib.App):
app.title = "Draw"
app.icon = nib.SFSymbol("pencil.tip")
app.width = 440
app.height = 380
# Canvas with gesture support
canvas = nib.Canvas(
width=420,
height=300,
background_color="#FFFFFF",
enable_gestures=True,
)
# Drawing state
last_x = 0.0
last_y = 0.0
stroke_color = "#000000"
stroke_width = 3
def on_pan_start(e: nib.PanEvent):
nonlocal last_x, last_y
last_x = e.x
last_y = e.y
def on_pan_update(e: nib.PanEvent):
nonlocal last_x, last_y
canvas.append(
nib.draw.Line(
x1=last_x,
y1=last_y,
x2=e.x,
y2=e.y,
stroke=stroke_color,
stroke_width=stroke_width,
line_cap="round",
)
)
last_x = e.x
last_y = e.y
canvas.on_pan_start = on_pan_start
canvas.on_pan_update = on_pan_update
# Clear canvas
def clear_canvas():
canvas.clear()
# Color selectors
def set_black():
nonlocal stroke_color
stroke_color = "#000000"
def set_red():
nonlocal stroke_color
stroke_color = "#e74c3c"
def set_blue():
nonlocal stroke_color
stroke_color = "#3498db"
def set_green():
nonlocal stroke_color
stroke_color = "#2ecc71"
# Width selectors
def set_thin():
nonlocal stroke_width
stroke_width = 2
def set_medium():
nonlocal stroke_width
stroke_width = 5
def set_thick():
nonlocal stroke_width
stroke_width = 10
app.build(
nib.VStack(
controls=[
# Toolbar
nib.HStack(
controls=[
nib.Button("Clear", action=clear_canvas),
nib.Spacer(),
nib.Text("Color:", font=nib.Font.CAPTION),
nib.Button("Black", action=set_black),
nib.Button("Red", action=set_red),
nib.Button("Blue", action=set_blue),
nib.Button("Green", action=set_green),
nib.Spacer(),
nib.Text("Width:", font=nib.Font.CAPTION),
nib.Button("Thin", action=set_thin),
nib.Button("Med", action=set_medium),
nib.Button("Thick", action=set_thick),
],
spacing=4,
),
# Canvas with border
nib.VStack(
controls=[canvas],
background=nib.Rectangle(
corner_radius=5,
stroke="#cccccc",
stroke_width=1,
),
),
],
spacing=10,
padding=10,
)
)
nib.run(main)
Walkthrough¶
Creating a Canvas¶
canvas = nib.Canvas(
width=420,
height=300,
background_color="#FFFFFF",
enable_gestures=True,
)
The Canvas view provides a Core Graphics-backed drawing surface. Key parameters:
| Parameter | Description |
|---|---|
width, height |
Canvas dimensions in points |
background_color |
Fill color for the canvas background |
enable_gestures |
When True, the canvas emits pan (drag) events |
Gesture handling¶
def on_pan_start(e: nib.PanEvent):
nonlocal last_x, last_y
last_x = e.x
last_y = e.y
def on_pan_update(e: nib.PanEvent):
nonlocal last_x, last_y
canvas.append(
nib.draw.Line(
x1=last_x, y1=last_y,
x2=e.x, y2=e.y,
stroke=stroke_color,
stroke_width=stroke_width,
line_cap="round",
)
)
last_x = e.x
last_y = e.y
canvas.on_pan_start = on_pan_start
canvas.on_pan_update = on_pan_update
The canvas fires gesture callbacks:
on_pan_start-- Called when the user begins a drag. Records the starting position.on_pan_update-- Called continuously as the user drags. Draws a line segment from the previous position to the current position.
The PanEvent object provides x and y coordinates relative to the canvas origin.
Drawing commands¶
canvas.append(
nib.draw.Line(
x1=last_x, y1=last_y,
x2=e.x, y2=e.y,
stroke=stroke_color,
stroke_width=stroke_width,
line_cap="round",
)
)
The nib.draw module provides drawing primitives that can be appended to a canvas:
nib.draw.Line-- A line segment between two pointsnib.draw.Rect-- A rectanglenib.draw.Circle-- A circlenib.draw.Path-- A custom path with arcs, curves, and linesnib.draw.Text-- Text rendered at a position
canvas.append() adds a drawing command incrementally. The Swift runtime renders it immediately without redrawing existing commands.
canvas.clear() removes all drawing commands and resets the canvas.
Using line_cap="round"¶
The line_cap parameter controls how line endpoints are drawn:
"round"-- Rounded endpoints (smooth freehand look)"square"-- Square endpoints extending beyond the endpoint"butt"-- Flat endpoints exactly at the endpoint (default)
For freehand drawing, "round" produces smooth joins between consecutive line segments.
Toolbar layout¶
The toolbar is an HStack with buttons and Spacer views:
nib.HStack(
controls=[
nib.Button("Clear", action=clear_canvas),
nib.Spacer(),
nib.Text("Color:", font=nib.Font.CAPTION),
nib.Button("Black", action=set_black),
...
nib.Spacer(),
nib.Text("Width:", font=nib.Font.CAPTION),
nib.Button("Thin", action=set_thin),
...
],
spacing=4,
)
Spacer pushes adjacent views apart, distributing the toolbar into three groups: the clear button on the left, color buttons in the middle, and width buttons on the right.
Drawing state¶
stroke_color = "#000000"
stroke_width = 3
Drawing settings are stored as plain Python variables. The color and width selector buttons update these variables via nonlocal. The next stroke drawn will use the new values. Previously drawn lines are not affected.
Canvas border¶
nib.VStack(
controls=[canvas],
background=nib.Rectangle(
corner_radius=5,
stroke="#cccccc",
stroke_width=1,
),
)
The canvas is wrapped in a VStack with a Rectangle background that provides a subtle border. The stroke parameter draws an outline, while corner_radius rounds the corners.
Running¶
nib run drawing_app.py