Skip to content

Commit ba7a53d

Browse files
fix(ios): crash when deleting a deployment ID
ForEach(enumerated, id: \.offset) with index-based bindings would read a stale/out-of-range index after a row was removed. Guard all index accesses in the get/set and the delete button. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 78afaf3 commit ba7a53d

1 file changed

Lines changed: 13 additions & 4 deletions

File tree

ios/App/ContentView.swift

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -407,18 +407,27 @@ private struct RelaySection: View {
407407
.font(.subheadline)
408408
.foregroundStyle(.secondary)
409409

410-
ForEach(Array(vpn.config.scriptIds.enumerated()), id: \.offset) { idx, id in
410+
ForEach(Array(vpn.config.scriptIds.enumerated()), id: \.offset) { idx, _ in
411411
HStack {
412412
TextField("#\(idx + 1)", text: Binding(
413-
get: { vpn.config.scriptIds[idx] },
413+
// Guard the index: after a delete, SwiftUI may
414+
// re-evaluate a removed row's binding before the
415+
// list updates — a stale index would crash.
416+
get: { idx < vpn.config.scriptIds.count ? vpn.config.scriptIds[idx] : "" },
414417
// Normalize on edit so a pasted full URL collapses
415418
// to the bare deployment ID (no prefix sticks).
416-
set: { vpn.config.scriptIds[idx] = VpnConfig.extractId($0); vpn.save() }
419+
set: {
420+
guard idx < vpn.config.scriptIds.count else { return }
421+
vpn.config.scriptIds[idx] = VpnConfig.extractId($0); vpn.save()
422+
}
417423
))
418424
.font(.system(.caption, design: .monospaced))
419425
.textInputAutocapitalization(.never)
420426
.autocorrectionDisabled()
421-
Button { vpn.config.scriptIds.remove(at: idx); vpn.save() } label: {
427+
Button {
428+
guard idx < vpn.config.scriptIds.count else { return }
429+
vpn.config.scriptIds.remove(at: idx); vpn.save()
430+
} label: {
422431
Image(systemName: "xmark.circle.fill")
423432
.foregroundStyle(.red)
424433
}

0 commit comments

Comments
 (0)