Course outline

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.

About 5 min readContent reviewed
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.

swift Illustrative Swift or SwiftUI fragment; requires the declared toolchain host.
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")
        }
    }
}
Expected output
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.

swift Illustrative Swift or SwiftUI fragment; requires the declared toolchain host.
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.
Expected output
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

Official references