iOS / Swift / Swift values and SwiftUI ownership / Lesson 6
Navigation values and presentation
Model destinations with stable values, keep route validation distinct from permission, and define cancellation explicitly.
On this page
Lesson goals
- Navigation changes interaction ownership
- Use a typed path for explicit destinations
- Keep dismiss capability at the right environment location
Navigation changes interaction ownership
Opening a detail screen creates a new interaction context with an input and a lifetime. Prefer passing a stable record identifier or small immutable value rather than an entire mutable parent screen. The data owner can then resolve current content and present missing or unavailable records honestly. A visible navigation link is not an authorization check. A deep link can reach the destination without passing through the normal menu, so malformed input and absent permission must be handled at their authoritative boundaries. Keep local presentation state distinct from shared domain state.
Use a typed path for explicit destinations
NavigationStack can bind its path to values that describe pushed destinations. The first fragment uses stable string identifiers and declares how a String destination is rendered. For a larger app, a dedicated Hashable route enum can prevent unrelated strings from being treated as the same destination category. The path records navigation state, not a cache of every mutable model. Decide what Back, programmatic reset, and restored path should mean before adding ad hoc dismissal calls throughout the view tree. NavigationStack requires the supported OS baseline, distinct from older navigation APIs.
struct WorkshopNavigation: View {
@State private var path: [String] = []
var body: some View {
NavigationStack(path: $path) {
NavigationLink("Open Maps", value: "w-7")
.navigationDestination(for: String.self) { id in
Text("Workshop \(id)")
}
.navigationTitle("Workshops")
}
}
}
Activating Open Maps pushes a destination showing Workshop w-7.
Requires SwiftUI and iOS 16 or later for NavigationStack. This course baseline is iOS 17 or later; no navigation was executed here.
Keep dismiss capability at the right environment location
The second fragment reads dismiss from the presented sheet’s environment. The value belongs to that presentation context, so the read should occur inside the presented content rather than assuming a parent’s dismiss acts on the sheet it is about to create. Dismissing closes the interaction; it does not by itself save or roll back a shared model. A cancellable editor needs a draft policy independent of the sheet mechanism. Define whether interactive dismissal is permitted with unsaved work and give users a clear recovery choice.
struct WorkshopSheet: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack {
Text("Workshop information")
Button("Done") { dismiss() }
}
}
}
// A parent presents WorkshopSheet with its sheet modifier.
Done requests dismissal of the sheet containing WorkshopSheet.
The presentation host is omitted. Calling dismiss outside an active applicable presentation has a different effect and is not a save operation.
Parse external routes before looking up data
A deep-link handler should validate the scheme, host, path shape, and required parameters according to its documented contract. Parsing only proves a supported destination can be described. The record may still be missing or inaccessible, and the application may need authentication before resolving it. Avoid substituting an arbitrary default record when parsing fails because that can make an invalid link look successful. Keep unsupported links in a clear error or fallback flow. Do not put credentials or sensitive user content into URLs simply because navigation can carry strings.
Distinguish navigation restoration from data restoration
Restoring a path can help return to a previous destination, but each stored identifier must still be resolved under the current account and application version. An old route can refer to deleted data or a record belonging to a signed-out session. Version restoration formats when necessary and avoid persisting transient view objects or closures. Keep the saved path small and explicit. If the app cannot restore a destination safely, return to a meaningful location with an explanation rather than forcing stale state into a new ownership scope.
Exercise every way a user can leave
Test normal push and Back, sheet Done, interactive dismissal, cancellation with a draft, direct external entry, unavailable data, and logout while a destination is open. Verify focus and accessibility context move meaningfully after presentation changes. A passing pure route parser test cannot establish actual universal-link configuration, system handoff, or device navigation. Those need authorized target tests with the selected app configuration. Keep the course’s expected presentation results separate from real simulator or device evidence, since no Apple SDK execution was performed during authoring.
Common errors and fixes
Dismiss targets the wrong presentation
- Why it happens
- The environment action was read from the parent’s context.
- How to fix it
- Read dismiss within the presented content that owns the interaction.
- Prevent it next time
- Test nested presentations and their close actions.
A deep link opens a plausible but wrong record
- Why it happens
- Invalid route input silently selected a default identifier.
- How to fix it
- Reject unsupported route shapes and show a meaningful fallback.
- Prevent it next time
- Include malformed and missing-parameter links in tests.
A restored path reveals old account data
- Why it happens
- Identifiers were restored without current ownership checks.
- How to fix it
- Resolve under the current session and enforce authoritative permission.
- Prevent it next time
- Test restoration after logout and account change.
Check your understanding
1. Why should a route carry w-7 instead of the seventh array position?
Show worked solution
A position changes when filtering, sorting, or refreshing. A stable identifier continues to name the intended record, which the owner can resolve or report as unavailable. The route then remains meaningful independently of the original list snapshot.
2. Does closing a sheet automatically cancel edits made through shared bindings?
Show worked solution
No. Shared bindings may already have changed the source. For a cancellable editor, use an independent draft and commit only through Save. Dismissal controls presentation lifetime; cancellation and persistence are separate application decisions that require explicit tests.
Key takeaways
- Navigation changes interaction ownership
- Exercise every way a user can leave