SwiftUI:从 ForEach 中删除项目会导致索引超出范围

时间:2021-02-18 04:41:41

标签: swift swiftui

我在我的 ContentView 组件中构建了一个水平滚动的 ForEach UI,它显示了一组自定义对象(结构形式)。当我尝试删除项目时,我收到“致命错误:索引超出范围”错误。

问题是当我删除一个项目时,实际的数组本身会更新,但特定的 AssetView(下面的组件)组件没有更新,因此它最终会迭代到不再存在的索引。知道可能是什么问题吗?下面是我的代码:

内容视图

    List<WebElement> NameColumns = table.findElements(By.xpath("//tr/td[" + col_name_position + "]"));
    for (WebElement e : NameColumns) {
        String[] strArray = new String[3];
        strArray[?] = (e.getText());
        System.out.println(e.getText());
    }

资产视图

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)
                       }
              }
    }
}

资产商店

这是我将所有资产对象读/写到我的应用程序的 Documents 文件夹的地方。

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)
        })
    }
}

资产对象

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!)
        
    }
  
}

1 个答案:

答案 0 :(得分:2)

我认为这是因为您的 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
                }
            }
        }
    }