Skip to content

Commit da47f06

Browse files
authored
Merge pull request #2094 from meshtastic/fix/lockdown-fullscreencover-ios17-launch-crash
fix: iOS 17 launch crash in the Lockdown gate presentation binding
2 parents 963e811 + f4ae4e6 commit da47f06

1 file changed

Lines changed: 23 additions & 4 deletions

File tree

Meshtastic/Views/ContentView.swift

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ struct ContentView: View {
2222
/// non-blocking state (.none, .unlocked, .lockNowAcknowledged).
2323
private var isLockdownGateActive: Bool { lockdown.isBlockingSession }
2424

25+
/// Plain view-state mirror of `isLockdownGateActive`, kept in sync from the
26+
/// coordinator via `.onChange`. Presenting the lockdown `fullScreenCover`
27+
/// through a *computed* `Binding` — getter reading the `lockdown`
28+
/// `@EnvironmentObject`, setter a no-op — produced a presentation binding that
29+
/// could never converge, which iOS 17's SwiftUI resolved by re-entering the
30+
/// attribute graph until it tripped `_assertionFailure` at first layout (a
31+
/// launch crash unique to iOS 17; iOS 18+ tolerated it). Driving the cover
32+
/// from real `@State` breaks that cycle. `LockdownSheet` has no dismiss
33+
/// affordance, so this stays non-dismissable — only the coordinator leaving a
34+
/// blocking state clears it.
35+
@State private var isShowingLockdownGate: Bool = false
36+
2537
init(appState: AppState, router: Router) {
2638
self.appState = appState
2739
self.router = router
@@ -38,16 +50,23 @@ struct ContentView: View {
3850
DeviceOnboarding()
3951
}
4052
)
41-
.fullScreenCover(isPresented: Binding(
42-
get: { isLockdownGateActive },
43-
set: { _ in /* non-dismissable; coordinator state controls visibility */ }
44-
)) {
53+
.fullScreenCover(isPresented: $isShowingLockdownGate) {
4554
LockdownSheet()
4655
}
4756
.onAppear {
4857
if UserDefaults.firstLaunch {
4958
isShowingDeviceOnboardingFlow = true
5059
}
60+
// Present the gate if the device is already in a blocking state when
61+
// this view appears.
62+
isShowingLockdownGate = isLockdownGateActive
63+
}
64+
.onChange(of: isLockdownGateActive) { _, active in
65+
// Follow the coordinator's blocking state. The gate never closes from
66+
// user interaction (fullScreenCover has no interactive dismiss and
67+
// LockdownSheet exposes no dismiss control), so this is the only path
68+
// that shows or hides it.
69+
isShowingLockdownGate = active
5170
}
5271
.onChange(of: UserDefaults.showDeviceOnboarding) {_, newValue in
5372
isShowingDeviceOnboardingFlow = newValue

0 commit comments

Comments
 (0)