In the notch
A notch page gives an add-on a tab of its own in the open notch, beside the Shelf, Clipboard and Widgets. It shows one screen at a time, like a small app: a list, a detail, a note being written.
A page
Declare "notch-page" in the manifest's surfaces, then send a page. Its symbol goes in the pill under the open notch, after Ledge's own tabs, and its title is what VoiceOver reads for that tab.
let list = PageScreen("list", title: "Notes",
actions: [PageAction("new", symbol: "square.and.pencil", label: "New note")],
content: .list(id: "notes", rows: [
ListRow("n1", title: "Launch day", subtitle: "Beta 2 goes out at 10:00", detail: "12:41 PM"),
ListRow("n2", title: "Groceries", subtitle: "Oat milk, eggs, basil", detail: "Yesterday"),
]))
AddOnSession.show(notchPage: NotchPage(title: "Notes", symbol: "note.text", size: .medium, screen: list))Sending nil takes the page down, and its tab with it. An add-on has one page. People can hide it with Show in the notch on the add-on's page in Settings.
| Size | The open notch's well |
|---|---|
.small | 140 points tall: a few rows, a short status |
.medium | 220 points tall: a list or a note. The default |
.large | 300 points tall: longer writing |
Screens
A page shows one PageScreen. To move somewhere, send the page again with the next screen. Ledge slides forward when the new screen's depth is greater than the last, back when it's less, and redraws in place when the id is the same. Give each screen an id that stays the same while it shows the same thing, like "list" or "note-n1".
The header sits beside the camera, where the Shelf shows its file count and Clear:
- On the left, a back button when
backis true, then thetitleor a greycaption. On a Mac with a notch, the camera leaves room for only a word or two there, so Ledge puts the caption at the top of the page instead. Keep titles short, likeNotes
. - On the right, up to three round buttons. Mark one
destructiveand it's red.
let note = PageScreen("note-\(id)", depth: 1, caption: "Edited 12:41", back: true,
actions: [PageAction("copy", symbol: "doc.on.doc", label: "Copy note"),
PageAction("delete", symbol: "trash", label: "Delete note", destructive: true)],
content: .editor(id: "body", text: markdown, placeholder: "Start writing", autofocus: false))What a screen holds
A screen is made of a widget's parts (stacks, text, symbols, progress, buttons and spacers) and three parts only pages have:
| Part | Draws | Sends |
|---|---|---|
.list | Rounded rows: a title, then a grey line with the detail and subtitle. Scrolls when it's taller than the page | .pressed, with the list's id as control and the row's as item |
.empty | A symbol, a title and a line under it, for a screen with nothing in it yet | Nothing |
.editor | A Markdown editor that fills the page, with a formatting bar | .edited while typing, .ended when done |
List, empty and editor fill the space under the header. Other parts sit at the top. Rows are one line each, cut off with an ellipsis, so put the words that tell rows apart first.
Events
Every press arrives at onEvent with event.surface == Surface.notchPage, the control's id, and the screen it came from in event.screen:
handlers.onEvent = { event in
guard event.surface == Surface.notchPage else { return }
switch (event.control, event.action) {
case ("notes", .pressed): if let id = event.item { await notes.open(id) }
case (PageScreen.backControl, .pressed): await notes.showList()
case ("new", .pressed): await notes.create()
case ("body", .ended(let markdown)): await notes.save(markdown, from: event.screen)
default: break
}
}The editor
The editor's text is Markdown. Ledge styles it as it's typed and hands it back as Markdown, marks and all: headings, bold, italic, strikethrough, code, quotes, bulleted and numbered lists, and checklists (- [ ] and - [x]). A click on a checkbox ticks it. Return carries a list on, and Return on an empty item ends it. The formatting bar adds bold, italic, headings, bullets and checklists, and ⌘B, ⌘I and ⌘Z work as everywhere else.
The notch doesn't take the keyboard until someone clicks in the editor, or the screen opens with autofocus. The app they were in stays the active app. While they type, the notch stays open. Esc, the back button, a click in another app, another tab or the notch closing hands the keyboard back.
| Action | When | Use it to |
|---|---|---|
.edited(text) | A moment after each pause in typing | Update a list title or a word count. Saving here is fine, but not required |
.ended(text) | When the editor gives the keyboard back, with the final text | Save. A press of the back button or a header button arrives after it |
While someone is typing, Ledge keeps their text and cursor even when you send the same screen again, so it's safe to update the caption after each .edited. Keep the editor's id the same between sends. Use autofocus for a new, empty note, where typing straight away is what people want, and leave it off when opening an existing one.
Bringing the page up
The user opens the page from its tab. Your add-on can open it too, with LedgeHost.showPage(), just after they use one of your controls or while one of your activities needs attention, and keep the notch open while something they watch runs with holdOpen. Both need the notch-control capability.
Limits
- One page per add-on. A screen has at most 200 parts, nested at most 6 deep.
- A list has at most 100 rows, with ids unique within the list.
- Up to 3 header buttons. Ids are unique across the header and the content, and
backis taken when the screen has a back button. - One editor per screen, holding at most 50,000 characters. Ledge stops typing at the limit.
- Titles, captions and button labels are at most 60 characters. Any other text is at most 1,000.