iOS / Swift / Accessible systems and target evidence / Lesson 16
Permissions, scenes, and private state
Reconcile capabilities on scene changes while preserving user intent, account ownership, and privacy boundaries.
On this page
Lesson goals
- Scene activity is a lifecycle signal
- Represent permission states without collapsing them
- Refresh status separately from requesting permission
Scene activity is a lifecycle signal
An iOS app can contain multiple scenes, and a scene can become active, inactive, or backgrounded. A scene phase change is not a new user command and does not guarantee every final process event will be delivered before termination. Persist important drafts according to a deliberate policy rather than waiting for a last background callback. On activation, previously observed permission or account state may be obsolete. Refresh status where the current feature needs it, but do not automatically replay a reservation, payment, or other consequential action simply because the scene becomes visible again.
Represent permission states without collapsing them
Notification authorization can distinguish not determined, denied, authorized, provisional, and ephemeral states on applicable targets. The first fragment maps those categories into an application explanation and includes an unknown-case fallback for future SDK evolution. A real localized interface should use resource messages rather than the synthetic literals. Do not map every non-authorized state to an identical failure if a limited capability is useful. Conversely, permission alone does not prove a notification was scheduled, delivered, or noticed. Keep capability status separate from the operation outcome.
import UserNotifications
func authorizationLabel(_ status: UNAuthorizationStatus) -> String {
switch status {
case .notDetermined: return "Not requested"
case .denied: return "Denied"
case .authorized: return "Authorized"
case .provisional: return "Provisional"
case .ephemeral: return "Ephemeral"
@unknown default: return "Unknown status"
}
}
// authorizationLabel(.denied) -> "Denied"
Denied maps to Denied; unsupported future cases use Unknown status.
This maps an existing value only. It requests no permission and schedules no notification.
Refresh status separately from requesting permission
The second fragment expresses a scene-owned refresh trigger for a view with an injected callback. It does not request system authorization on every activation. The callback should coalesce duplicate status reads and guard stale completion according to request and session identity. Ask permission in a feature flow where the user understands its purpose and the fallback after denial. Repeated prompts cannot substitute for clear context, and some denied states require a different settings-oriented recovery under documented platform behavior. The course never invokes those system interactions.
struct CapabilityStatusView: View {
@Environment(\.scenePhase) private var phase
let refreshStatus: () -> Void
var body: some View {
Text("Capability status")
.onChange(of: phase) { _, next in
if next == .active { refreshStatus() }
}
}
}
A transition to active requests status refresh; it does not repeat a prior save.
Requires SwiftUI and the iOS17 two-value onChange form. The callback’s asynchronous ownership policy is supplied by the model.
Keep private state scoped to the current account
Logout should invalidate appropriate credentials, pending publication rights, private caches, and navigation state under one coordinated account transition. A late response from the previous account must not populate a still-alive view for the next user. Include session identity in result gates and resolve restored routes under current permission. Decide whether unsent drafts are retained, exported, or discarded according to the product’s explicit policy. Do not retain private content indefinitely merely because an observable model is application-scoped. A disabled menu entry is interaction feedback, not authoritative access control.
Treat the distributed app as an observable artifact
A compiled constant, asset file, or build setting embedded in the app is not a secret boundary. Keep privileged backend credentials on an authorized server and use platform-appropriate credential storage under its documented access and recovery rules. Review diagnostic logs and scene-switching presentation for unnecessary private content. Privacy decisions should follow the actual feature and data lifecycle, rather than collecting every payload because a debugging SDK permits it. Permission descriptions and required capability configuration must match what the app truly does; no client-side explanation grants the backend authority to act.
Test interruption and capability changes explicitly
A model fake can simulate denial, limited permission, settings changes, session expiry, and logout during a pending status read. Verify that obsolete results cannot publish into a new account and that activation refresh does not replay commands. Actual system permission dialogs, scene transitions, protected storage, and termination recovery require authorized Apple-hosted integration checks. Record the target and configuration actually exercised. No notification authorization, scene event, credential store, or device workflow was executed during authoring, so the examples remain illustrative and cannot certify platform behavior.
Common errors and fixes
Activation repeats a consequential command
- Why it happens
- Lifecycle visibility was mistaken for renewed intent.
- How to fix it
- Refresh status and require a deliberate command event.
- Prevent it next time
- Exercise repeated active transitions while work is pending.
Denied and provisional states look identical
- Why it happens
- Permission categories were collapsed without a feature policy.
- How to fix it
- Map documented states into explicit supported capabilities.
- Prevent it next time
- Test each applicable authorization category.
Previous-account data survives into a new session
- Why it happens
- Caches and result publication lacked account scope.
- How to fix it
- Invalidate under an account generation and review restoration.
- Prevent it next time
- Switch accounts before fake responses finish.
Check your understanding
1. Should a permission refresh automatically schedule the user’s previous notification?
Show worked solution
No. Status reconciliation and user commands are separate. A previous intent may have been cancelled, superseded, or already completed. Refresh capability state, then let an explicit current command decide whether scheduling is still appropriate under the product policy.
2. Does a private client constant protect a privileged API key?
Show worked solution
No. Swift access control governs source-level use, not confidentiality of a distributed artifact. Place privileged operations behind a server authorization boundary. Treat client configuration as observable and keep logs and persisted data within a reviewed privacy lifecycle.
Key takeaways
- Scene activity is a lifecycle signal
- Test interruption and capability changes explicitly