UIKit — automatic
TraceItX observes UIViewController.viewDidAppear and emits a crumb when the
appearing controller is either pushed onto a UINavigationController or
presented modally. The screen name is the view controller’s class name:
// Automatic — no code required.
navigationController?.pushViewController(CheckoutViewController(), animated: true)
// → breadcrumb: "CartViewController → CheckoutViewController"
Container churn and tab re-selections are deliberately excluded, so the trail stays readable rather than logging every internal re-appearance. That also means tab switches emit nothing — mark them explicitly if they matter.
SwiftUI — not automatic
A NavigationStack / NavigationLink push is not backed by discrete view
controllers, so there is nothing to observe. Mark screens from .onAppear:
struct CheckoutView: View {
var body: some View {
Form { … }
.onAppear { TraceItX.shared.recordScreen("Checkout") }
}
}
Markers and UIKit auto-capture share one chain, so a mixed app reads as a
single trail: RootViewController → Checkout → ConfirmationViewController.
How the chain behaves
- One global chain, chronological. Not per-stack. Note that tab switches
are excluded from auto-capture entirely (see above), so
TabA → TabBonly appears if you record it yourself. - The first screen emits nothing. There is no screen to come from yet. It
still becomes the
fromof the next transition. A → Ais suppressed. Re-appearances from backgrounding or a dismissed modal never produce a duplicate crumb.- Blank names are dropped, and
recordScreenis a no-op beforestart(). - Failures stay silent. Marker calls never throw into your navigation.
Naming screens
Route identifiers, never user content. OrderDetail, not
Order #4471 — jane@example.com. Names travel in the report and are shown
in triage. Screen names do pass through the breadcrumb buffer’s redaction, so an
obvious email or card number is caught — but redaction recognises shapes, not
meaning, and an order id or an internal username sails through. Do not rely on
it. Ids belong in the optional data map:
TraceItX.shared.recordScreen("OrderDetail", data: ["orderId": orderId])
Class names are stable and safe by default, which is part of why UIKit auto-capture uses them.
What lands in the envelope
{
"t": 1755500000000,
"seq": 42,
"kind": "navigation",
"message": "Cart → Checkout",
"data": {
"from": "Cart",
"to": "Checkout"
}
}
t is the shared epoch-millisecond clock that also stamps the session replay.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| Only one screen ever appears | SwiftUI-only navigation, or navigation that is neither a push nor a modal presentation. Mark screens from .onAppear. |
| Tab switches missing | Expected — tab re-appearances are excluded from auto-capture. Mark them explicitly. |
| Crumbs stop when the reporter opens | Expected. The trail is frozen so the reporter’s own UI never pollutes the evidence. |