簡體   English   中英

刪除 ForEach 中的數組元素時 SwiftUI 超出索引

[英]SwiftUI out of index when deleting an array element in ForEach

我在這里查看了不同的問題,但不幸的是我找不到答案。 這是我的代碼:

SceneDelegate.swift

...
let contentView = ContentView(elementHolder: ElementHolder(elements: ["abc", "cde", "efg"]))
...
window.rootViewController = UIHostingController(rootView: contentView)

ContentView.swift

class ElementHolder: ObservableObject {

    @Published var elements: [String]

    init(elements: [String]) {
        self.elements = elements
    }
}

struct ContentView: View {

    @ObservedObject var elementHolder: ElementHolder

    var body: some View {
        VStack {

            ForEach(self.elementHolder.elements.indices, id: \.self) { index in
                SecondView(elementHolder: self.elementHolder, index: index)
            }

        }
    }
}

struct SecondView: View {

    @ObservedObject var elementHolder: ElementHolder
    var index: Int

    var body: some View {
        HStack {
            TextField("...", text: self.$elementHolder.elements[self.index])
            Button(action: {
                self.elementHolder.elements.remove(at: self.index)
            }) {
                Text("delete")
            }
        }
    }

}

按下刪除按鈕時,應用程序因索引越界錯誤而崩潰。

有兩個奇怪的事情,應用程序正在運行時

1)您刪除VStack並將ForEach ContentView.swift body

2)你把SecondView的代碼直接放到ForEach

只有一件事:我真的需要ObservableObject ,這段代碼只是另一個代碼的簡化。

更新

我更新了我的代碼並將Text更改為TextField ,因為我不能只傳遞一個字符串,我需要雙向連接。

問題出在單擊刪除按鈕時執行更新的順序。

按下按鈕時,將發生以下情況:

  1. 元素持有者的elements屬性被改變
  2. 這會通過objectWillChange發布者發送通知,該發布者是ElementHolder的一部分,由ObservableObject協議聲明。
  3. 訂閱此發布者的視圖會收到一條消息,並將更新其內容。
    1. SecondView 接收通知並通過執行body getter 更新其視圖。
    2. ContentView 接收通知並通過執行body getter 更新其視圖。

為了讓代碼不崩潰,3.1 必須在 3.2 之后執行。 盡管(據我所知)無法控制此順序。

最優雅的解決方案是在 SecondView 中創建一個onDelete閉包,它將作為參數傳遞。

這也將解決元素視圖可以訪問所有元素的架構反模式,而不僅僅是它顯示的元素。

整合所有這些將產生以下代碼:

struct ContentView: View {
    @ObservedObject var elementHolder: ElementHolder

    var body: some View {
        VStack {
            ForEach(self.elementHolder.elements.indices, id: \.self) { index in
                SecondView(
                    element: self.elementHolder.elements[index],
                    onDelete: {self.elementHolder.elements.remove(at: index)}
                )
            }
        }
    }
}

struct SecondView: View {
    var element: String
    var onDelete: () -> ()

    var body: some View {
        HStack {
            Text(element)
            Button(action: onDelete) {
                Text("delete")
            }
        }
    }
}

有了這個,甚至可以刪除 ElementHolder 並只擁有一個@State var elements: [String]變量。

這是可能的解決方案 - 使SecondView的主體不依賴於ObservableObject

用 Xcode 11.4 / iOS 13.4 測試 - 沒有崩潰

struct SecondView: View {

    @ObservedObject var elementHolder: ElementHolder
    var index: Int
    let value: String

    init(elementHolder: ElementHolder, index: Int) {
        self.elementHolder = elementHolder
        self.index = index
        self.value = elementHolder.elements[index]
    }

    var body: some View {
        HStack {
            Text(value)     // not refreshed on delete
            Button(action: {
                self.elementHolder.elements.remove(at: self.index)
            }) {
                Text("delete")
            }
        }
    }
}

另一種可能的解決方案是不要在SecondView中觀察ElementHolder ...不需要呈現和刪除它 - 也不會崩潰

struct SecondView: View {

    var elementHolder: ElementHolder // just reference
    var index: Int

    var body: some View {
        HStack {
            Text(self.elementHolder.elements[self.index])
            Button(action: {
                self.elementHolder.elements.remove(at: self.index)
            }) {
                Text("delete")
            }
        }
    }
}

更新:文本字段的SecondView變體(僅更改的是文本字段本身)

struct SecondViewA: View {

    var elementHolder: ElementHolder
    var index: Int

    var body: some View {
        HStack {
            TextField("", text: Binding(get: { self.elementHolder.elements[self.index] },
                set: { self.elementHolder.elements[self.index] = $0 } ))
            Button(action: {
                self.elementHolder.elements.remove(at: self.index)
            }) {
                Text("delete")
            }
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM