使用 onDelete 时索引超出范围
Posted
技术标签:
【中文标题】使用 onDelete 时索引超出范围【英文标题】:Getting Index out of range when using onDelete 【发布时间】:2021-05-07 08:34:21 【问题描述】:当我尝试删除列表中的项目时,出现以下错误:
Swift/ContiguousArrayBuffer.swift:580:致命错误:索引超出范围 2021-05-07 09:59:38.171277+0200 Section[4462:220358] Swift/ContiguousArrayBuffer.swift:580:致命错误:索引超出范围
我一直在寻找解决方案,但我发现的所有解决方案似乎都不适用于我的代码。
FIY 这是我在 SwiftUI 或 Swift 中的第一个应用程序,所以我对此完全陌生。
所以如果有人可以帮助我并解释我需要改变什么以及为什么我会如此感激:)
这里有一些代码,希望能帮助你找到问题的根源。
我的模特:
//
// Item.swift
// Section
//
// Created by Niklas Peterson on 2021-03-11.
//
import Foundation
struct Item: Identifiable, Hashable, Codable
var id = UUID().uuidString
var name: String
extension Item
static func getAll() -> [Item]
let key = UserDefaults.Keys.items.rawValue
guard let items: [Item] = UserDefaults.appGroup.getArray(forKey: key) else
let items: [Item] = [.section1]
UserDefaults.appGroup.setArray(items, forKey: key)
return items
return items
static let section1: Item =
return Item(name: "Section")
()
extension Item
static func fromId(_ id: String) -> Item?
getAll().first $0.id == id
用户默认值:
//
// UserDefaults+Ext.swift
// Section
//
// Created by Niklas Peterson on 2021-03-11.
//
import Foundation
extension UserDefaults
static let appGroup = UserDefaults(suiteName: "************ hidden ;) ")!
extension UserDefaults
enum Keys: String
case items
extension UserDefaults
func setArray<Element>(_ array: [Element], forKey key: String) where Element: Encodable
let data = try? JSONEncoder().encode(array)
set(data, forKey: key)
func getArray<Element>(forKey key: String) -> [Element]? where Element: Decodable
guard let data = data(forKey: key) else return nil
return try? JSONDecoder().decode([Element].self, from: data)
内容视图:
//
// ContentView.swift
// Section
//
// Created by Niklas Peterson on 2021-03-11.
//
import SwiftUI
struct ContentView: View
@State private var items = Item.getAll()
func saveItems()
let key = UserDefaults.Keys.items.rawValue
UserDefaults.appGroup.setArray(items, forKey: key)
func move(from source: IndexSet, to destination: Int)
items.move(fromOffsets: source, toOffset: destination)
saveItems()
func delete(at offsets: IndexSet)
items.remove(atOffsets: offsets)
saveItems()
var body: some View
NavigationView
List
ForEach(items.indices, id: \.self) index in
TextField("", text: $items[index].name, onCommit:
saveItems()
)
.onDelete(perform: delete)
.onMove(perform: move)
.toolbar
ToolbarItemGroup(placement: .primaryAction)
HStack
Button(action:
self.items.insert(Item(name: ""), at: 0)
)
Image(systemName: "plus.circle.fill")
EditButton()
.navigationBarTitle("Sections")
.listStyle(InsetGroupedListStyle())
【问题讨论】:
随便搜一下——原因和解决方法被贴了很多次 @asperi 我之前已经阅读过你对这个问题的其他问题的回答,但我似乎无法让它发挥作用。并且不完全理解我需要做什么才能使更改适合我的情况。我刚试过这个:***.com/questions/66712572/… 但那也不管用我通常只做设计,但想学习如何在 SwiftUI 中编码 :) 【参考方案1】:注意:这在ios15
中不再是问题,因为List
现在支持binding
这似乎是SwiftUI
本身的一个错误。
经过研究,发现问题来自TextField
绑定。如果将TextField
替换为简单的Text
视图,一切都会正常工作。看起来删除一个项目后,TextField
绑定试图访问deleted
项目,但找不到它导致崩溃。
这篇文章帮我解决了这个问题,查看SwiftbySundell
因此,为了解决这个问题,我们将不得不更深入地研究 Swift 的集合 API,以使我们的数组索引真正独一无二。
-
引入一个自定义集合,它将另一个集合的索引与其包含的元素的标识符相结合。
struct IdentifiableIndices<Base: RandomAccessCollection>
where Base.Element: Identifiable
typealias Index = Base.Index
struct Element: Identifiable
let id: Base.Element.ID
let rawValue: Index
fileprivate var base: Base
-
使我们的新集合符合标准库的
RandomAccessCollection
协议,
extension IdentifiableIndices: RandomAccessCollection
var startIndex: Index base.startIndex
var endIndex: Index base.endIndex
subscript(position: Index) -> Element
Element(id: base[position].id, rawValue: position)
func index(before index: Index) -> Index
base.index(before: index)
func index(after index: Index) -> Index
base.index(after: index)
-
通过将以下计算属性添加到所有兼容的基本集合(即支持随机访问并且还包含
Identifiable
元素的基本集合),可以轻松创建IdentifiableIndices
实例:
extension RandomAccessCollection where Element: Identifiable
var identifiableIndices: IdentifiableIndices<Self>
IdentifiableIndices(base: self)
-
最后,让我们也扩展 SwiftUI 的
ForEach
类型,使用方便的 API,让我们可以迭代 IdentifiableIndices
集合,而无需手动访问每个索引的 rawValue
:
extension ForEach where ID == Data.Element.ID,
Data.Element: Identifiable,
Content: View
init<T>(
_ data: Binding<T>,
@ViewBuilder content: @escaping (T.Index, Binding<T.Element>) -> Content
) where Data == IdentifiableIndices<T>, T: MutableCollection
self.init(data.wrappedValue.identifiableIndices) index in
content(
index.rawValue,
Binding(
get: data.wrappedValue[index.rawValue] ,
set: data.wrappedValue[index.rawValue] = $0
)
)
-
最后,在您的
ContentView
中,您可以将ForEach
更改为:
ForEach($items) index, item in
TextField("", text: item.name, onCommit:
saveItems()
)
@Binding
属性包装器让我们可以声明一个值实际上来自其他地方,并且应该在两个地方共享。在我们的列表中删除Item
时,数组items
会迅速变化,根据我的经验,这会导致问题。
似乎SwiftUI
对它创建的集合绑定应用了某种形式的缓存,这可能会导致在下标到我们的底层Item
数组时使用过时的索引——这会导致应用程序因超出范围而崩溃边界错误。
【讨论】:
我刚刚测试了它,它可以工作!我不知道是 TextField 导致了崩溃。我已经搜索了几天并尝试了我在 *** 上找到的所有解决方案。认为这与许多人一直在提到的 List 或 ForEach 上的 OnDelete 错误有关。我会再读一遍你写的所有东西,以试图理解它。并检查您发布的链接!你让我的星期五变得更好了!非常感谢!以上是关于使用 onDelete 时索引超出范围的主要内容,如果未能解决你的问题,请参考以下文章
SwiftUI .onDelete 抛出致命错误:索引超出范围