[英]SwiftUI: Deleting an item from a ForEach results in Index Out of Range
我在我的 ContentView 組件中構建了一個水平滾動的 ForEach UI,它顯示了一組自定義對象(結構形式)。 當我嘗試刪除項目時,我收到“致命錯誤:索引超出范圍”錯誤。
問題是當我刪除一個項目時,實際的數組本身會更新,但特定的 AssetView(下面的組件)組件沒有更新,因此它最終會迭代到不再存在的索引。 知道問題可能是什么嗎? 下面是我的代碼:
struct ContentView: View {
@ObservedObject var assetStore: AssetStore
var body: some View {
ScrollView (.horizontal) {
ForEach(assetStore.assets.indices, id: \.self) { index in
AssetView(
asset: $assetStore.assets[index],
assetStore: assetStore,
smallSize: geo.size.height <= 667
)
.padding(.bottom)
}
}
}
}
struct AssetView: View {
@Binding var asset: Asset
@ObservedObject var assetStore: AssetStore
var smallSize: Bool
@State var editAsset: Bool = false
var body: some View {
VStack(alignment: .center, spacing: smallSize ? -10 : 0) {
HStack {
TagText(tagName: asset.name)
.onTapGesture {
editAsset.toggle()
}
Spacer()
DisplayCurrentValues(
forCurrentValueText: asset.getCurrentValueString,
forCurrentValueLabel: "Current Value"
)
.onTapGesture {
editAsset.toggle()
}
}
DisplayStepper(asset: $asset, title: "YoY Growth", type: .growth)
DisplayStepper(asset: $asset, title: "Recurring Deposit", type: .recurring)
}
.sheet(isPresented: $editAsset, content: {
EditAsset(assetStore: assetStore, currentValue: String(asset.currentValue), name: asset.name, asset: $asset)
})
}
}
這是我將所有資產對象讀/寫到我的 App 的 Documents 文件夾的地方。
class AssetStore: ObservableObject {
let assetsJSONURL = URL(fileURLWithPath: "Assets",
relativeTo: FileManager.documentsDirectoryURL).appendingPathExtension("json")
@Published var assets: [Asset] = [] {
didSet {
saveJSONAssets()
}
}
init() {
print(assetsJSONURL)
loadJSONAssets()
}
private func loadJSONAssets() {
guard FileManager.default.fileExists(atPath: assetsJSONURL.path) else {
return
}
let decoder = JSONDecoder()
do {
let assetsData = try Data(contentsOf: assetsJSONURL)
assets = try decoder.decode([Asset].self, from: assetsData)
} catch let error {
print(error)
}
}
private func saveJSONAssets() {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
do {
let assetsData = try encoder.encode(assets)
try assetsData.write(to: assetsJSONURL, options: .atomicWrite)
} catch let error {
print(error)
}
}
public func deleteAsset(atIndex id: Asset) {
let index = assets.firstIndex(where: { $0.id == id.id})
assets.remove(at: index!)
}
}
struct Asset: Identifiable, Codable, Hashable {
// currently set for 10 years
let id = UUID()
enum Frequency: String, Codable, CaseIterable {
case month = "Month"
case year = "Year"
case none = "None"
}
let years: Int
var name: String
var currentValue: Int
var growth: Int
var recurringDeposit: Int
var recurringFrequency: Frequency
enum CodingKeys: CodingKey {
case id
case years
case name
case currentValue
case growth
case recurringDeposit
case recurringFrequency
}
init(
name: String = "AssetName",
currentValue: Int = 1000,
growth: Int = 10,
years: Int = 10,
recurringDeposit: Int = 100,
recurringFrequency: Frequency = .month
) {
self.name = name
self.currentValue = currentValue
self.growth = growth
self.recurringDeposit = recurringDeposit
self.recurringFrequency = recurringFrequency
self.years = years
}
}
我認為這是因為您的ForEach
依賴於索引,而不是Asset
本身。 但是,如果你擺脫了indices
,你將不得不為Asset
編寫一個新的綁定。 這是我認為它可能的樣子:
ForEach(assetStore.assets, id: \.id) { asset in
AssetView(
asset: assetStore.bindingForId(id: asset.id),
assetStore: assetStore,
smallSize: geo.size.height <= 667
)
.padding(.bottom)
}
然后在您的AssetStore
中:
func bindingForId(id: UUID) -> Binding<Asset> {
Binding<Asset> { () -> Asset in
self.assets.first(where: { $0.id == id }) ?? Asset()
} set: { (newValue) in
self.assets = self.assets.map { asset in
if asset.id == id {
return newValue
} else {
return asset
}
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.