简体   繁体   English

SwiftUI 为没有显式索引的应用程序添加元素时抛出“致命错误:索引超出范围”

[英]SwiftUI throwing 'Fatal error: Index out of range' when adding element for app with no explicit indexing

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 ?为什么在以下应用程序中单击“不错的餐厅”并尝试添加贡献者时,应用程序会崩溃并出现错误: 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. Xcode 调试器中的错误没有明显有用的堆栈跟踪,并直接指向“@main”行。

There are no explicit array indices used in the code nor any uses of members like .first .代码中没有使用显式数组索引,也没有使用.first等成员。

I'm using Xcode Version 13.4.1 (13F100) I'm using simulator: iPhone 13 iOS 15.5 (19F70)我正在使用 Xcode 版本 13.4.1 (13F100) 我正在使用模拟器: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:我注意到:

  • If I unroll the ForEach($checks) the crash doesn't occur (but I need to keep the ForEach so I can list all the checks)如果我展开 ForEach($checks) 不会发生崩溃(但我需要保留 ForEach 以便列出所有检查)
  • If I don't take a binding to the CheckContribution ( ForEach($check.contributions) { $contribution in then the crash doesn't occur (but I need the binding so subviews can modify the CheckContribution如果我不对 CheckContribution ( ForEach($check.contributions) { $contribution in绑定,则不会发生崩溃(但我需要绑定,以便子视图可以修改 CheckContribution
  • If I don't set the selectedContributor then the crash doesn't occur (but I need the selectedContributor in the real app for navigation purposes)如果我不设置 selectedContributor 则不会发生崩溃(但我需要在真实应用程序中使用 selectedContributor 进行导航)

The 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.我能找到实际可行的最干净的方法是将嵌套的ForEach进一步分离到一个子视图中并将contributors数组绑定到它。

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.我仍然不确定 SwiftUI 的内部结构,以及它为什么会这样工作。 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:如果您真的希望Button位于List中,那么您可以使用单独的视图尝试这种方法,对我来说效果很好:

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.我有同样的错误,但使用 tabview。 Moreover, the fall was only on iOS 15, but on iOS 16 it worked perfectly and there were no falls.此外,仅在 iOS 15 上掉线,但在 iOS 16 上运行完美,没有掉线。

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).在调试的过程中找到了解决办法:我注意到它甚至在 predstavlenie 出现之前就在下降(它起作用了出现)。 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.它起作用了——iOS 15 上没有更多的崩溃。显然在 iOS 16 之前处理空的 arrays 有一些问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM