Why, in the following app when clicking through to 'Nice Restaurant' and trying to add a contributor, does the app crash with the error: Swift/ContiguousArrayBuffer.swift:575: Fatal error: Index out of range
?
The error, in the Xcode debugger, has no obviously useful stack trace and points straight at the '@main' line.
There are no explicit array indices used in the code nor any uses of members like .first
.
I'm using Xcode Version 13.4.1 (13F100) I'm using simulator: iPhone 13 iOS 15.5 (19F70)
import SwiftUI
struct CheckContribution: Identifiable {
let id: UUID = UUID()
var name: String = ""
}
struct Check: Identifiable {
var id: UUID = UUID()
var title: String
var contributions: [CheckContribution]
}
let exampleCheck = {
return Check(
title: "Nice Restaurant",
contributions: [
CheckContribution(name: "Bob"),
CheckContribution(name: "Alice"),
]
)
}()
struct CheckView: View {
@Binding var check: Check
@State private var selectedContributor: CheckContribution.ID? = nil
func addContributor() {
let newContribution = CheckContribution()
check.contributions.append(newContribution)
selectedContributor = newContribution.id
}
var body: some View {
List {
ForEach($check.contributions) { $contribution in
TextField("Name", text: $contribution.name)
}
Button(action: addContributor) {
Text("Add Contributor")
}
}
}
}
@main
struct CheckSplitterApp: App {
@State private var checks: [Check] = [exampleCheck]
var body: some Scene {
WindowGroup {
NavigationView {
List {
ForEach($checks) { $check in
NavigationLink(destination: {
CheckView(check: $check)
}) {
Text(check.title).font(.headline)
}
}
}
}
}
}
}
I've noticed that:
ForEach($check.contributions) { $contribution in
then the crash doesn't occur (but I need the binding so subviews can modify the CheckContributionThe cleanest way I could find that actually works is to further separate the nested ForEach
into a subview and bind the contributors
array to it.
struct CheckView: View {
@Binding var check: Check
@State private var selectedContributor: CheckContribution.ID? = nil
func addContributor() {
let newContribution = CheckContribution()
check.contributions.append(newContribution)
selectedContributor = newContribution.id
}
var body: some View {
List {
ContributionsView(contributions: $check.contributions)
Button(action: addContributor) {
Text("Add Contributor")
}
// Test that changing other properties still works.
Button("Change title", action: changeTitle)
}
.navigationTitle(check.title)
}
func changeTitle() {
check.title = "\(Int.random(in: 1...100))"
}
}
struct ContributionsView: View {
@Binding var contributions: [CheckContribution]
var body: some View {
ForEach($contributions) { $contribution in
TextField("Name", text: $contribution.name)
}
}
}
I'm still not sure about the internals of SwiftUI, and why it works this way. I hope it helps. And maybe another more experienced user can provide a clear explanation to this.
If you really want the Button
to be in the List
, then you could try this approach using a separate view, works well for me:
struct CheckView: View {
@Binding var check: Check
@State private var selectedContributor: CheckContribution.ID? = nil
var body: some View {
List {
ForEach($check.contributions) { $contribution in
TextField("Name", text: $contribution.name)
}
AddButtonView(check: $check) // <-- here
}
}
}
struct AddButtonView: View {
@Binding var check: Check
func addContributor() {
let newContribution = CheckContribution(name: "new contribution")
check.contributions.append(newContribution)
}
var body: some View {
Button(action: addContributor) {
Text("Add Contributor")
}
}
}
I had the same error but with tabview. Moreover, the fall was only on iOS 15, but on iOS 16 it worked perfectly and there were no falls.
I tried both through indexes, and through checking for finding the index inside the range, but nothing helped.
The solution was found in the process of debugging: I noticed that it was falling even before the predstavlenie appeared (it worked Appear). I did a simple check to see if the data array is empty
if !dataArray.isEmpty {
TabView(selection: $selection) {
ForEach(dataArray, id: \.self) { item in
...
}
}
}
And it worked - there were no more crashes on iOS 15. Apparently there was some problem with the processing of empty arrays before iOS 16.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.