Swift Markdown Kit · DOCS
Interaction
Interaction
What happens when the reader taps a link, an image, a copy button or a retry button — and how to take any of it over.
The rule
The SDK draws one line: does the action stay inside the rendered content?
| Interaction | Who acts | Can you change it? |
|---|---|---|
| Copy (code block, message) | SDK, always | Behaviour no. Whether the button exists, yes. |
| Reasoning fold | SDK, always | Labels and default state, yes. |
| Task checkbox | SDK, always | — |
| Link | SDK by default | Yes — one line. |
| Image | SDK by default | Yes — one line. |
| Retry, edit | You, always | The SDK cannot re-run your request. |
| Custom syntax tap | You, always | — |
Copying and folding need nothing from your app, so delegating them would only be work for you. Opening a link or a picture leaves the rendered surface, and there your app may legitimately want the last word.
Links and images
Do nothing and both work: links open in an in-app SFSafariViewController,
images open in a full-screen viewer with pinch zoom, double-tap and drag-to-
dismiss.
To take one back:
var options = MarkdownChatRenderOptions()
options.interaction.images = .handledByHost // links stay automatic
MarkdownChatRenderView(messages: messages, options: options) { event in
guard case .action(let event) = event,
case .imageTapped(let url) = event.action, let url else { return }
presentMyOwnViewer(url)
}
Three policies, per interaction:
| Policy | What happens | What you hear |
|---|---|---|
.automatic (default) | The SDK opens it in-app. | didOpenLink / didPresentImage — a notification. |
.handledByHost | Nothing. | linkTapped / imageTapped — act on it or nothing happens. |
.disabled | Nothing, and the element stops looking tappable. | Nothing. |
options.interaction = .manual sets both to .handledByHost at once.
Notifications are not vetoes
The two families of event are deliberately distinct:
- Requests —
linkTapped,imageTapped,extensionTapped. The SDK did nothing. Act on them or nothing happens. - Notifications —
didOpenLink,didPresentImage,codeCopied,reasoningToggled. The SDK already did it. Acting again opens the same thing twice.
There is no callback that can cancel an automatic action. To take control, change the policy — that is what it is for.
Link safety
Rendered text is model output, and a model can be talked into emitting
javascript:, file://, or a deep link into your own app. A scheme outside the
allowlist is therefore never opened and never handed to you:
options.interaction.allowedLinkSchemes // ["http", "https", "mailto"] by default
options.interaction.allowedLinkSchemes.insert("myapp")
Anything else arrives as linkBlocked(url:scheme:) so you can log it. This
applies under every policy, including .handledByHost — an app that took over
link handling is still not a reason to hand it javascript:.
Both renderers also refuse any navigation that would replace the rendered page. Link taps are cancelled inside the page and routed through the bridge, so a navigation reaching that layer is unexpected by definition.
Copy
Copying is never delegated: the SDK writes to the pasteboard and tells you what happened. What you can change is whether the affordance exists at all, which some apps must be able to remove for data-loss-prevention reasons:
options.interaction.allowsCodeCopy = false // no copy button on code blocks
options.messageActions.assistantActions = [] // no toolbar under messages
MarkdownChatRenderView(messages: messages) { event in
guard case .action(let event) = event else { return }
if case .codeCopied = event.action { showToast("Copied") }
if case .codeCopyFailed(_, _, let error) = event.action { showToast(error.message) }
}
Retry and edit
The SDK cannot re-run a request or write into a composer it does not own, so
these buttons appear only when you list them — and listing them is your promise
to handle messageAction. See
Streaming & Chat.
Custom syntax
A tap on a registered inline extension — an @mention, a [[wikilink]] —
arrives as extensionTapped(name:action:value:) and is always yours to route.
See Theming & Syntax.