SwiftUI 中的可扩展列表只会扩展一次,不会收缩

Expandable List in SwiftUI only expands once and won't contract

我正在尝试(但失败了!)在 SwiftUI 中实现可扩展的问答列表。

struct FAQ: Equatable, Identifiable {
    static func ==(lhs: FAQ, rhs: FAQ) -> Bool {
        return lhs.id == rhs.id
    }
    let id = UUID()
    let question: String
    let answers: [String]
    var isExpanded: Bool = false
}

struct ContentView: View {
    @State private (set) var faqs: [FAQ] = [
        FAQ(question: "What is the fastest animal on land?", answers: ["The cheetah"]),
        FAQ(question: "What colours are in a rainbox?", answers: ["Red", "Orange", "Yellow", "Blue", "Indigo", "Violet"])
    ]

    var body: some View {
        List {
            ForEach(faqs) { faq in
                Section(header: Text(faq.question)
                    .onTapGesture {
                        if let index = self.faqs.firstIndex(of: faq) {
                            self.faqs[index].isExpanded.toggle()
                        }
                    }
                ) {
                    if faq.isExpanded {
                        ForEach(faq.answers, id: \.self) {
                            Text("• " + [=10=]).font(.body)
                        }
                    }
                }
            }
        }
    }
}

点击任何问题成功将答案展开,但再次点击相同的 header 不会缩小答案,点击第二个问题也不会展开这些答案。

明智地放置了一些 prints,我可以看到 isExpanded 在点击第一个问题时切换到 true,但之后不会切换回 false.

谁能解释一下我做错了什么?

问题出在您的 @State var faq: [FAQ] 行上。 @State 属性 包装器允许您的视图观察数组中的更改,但更改数组元素之一的 属性 不算作数组中的更改。

您可能想要创建一个小视图模型,而不是使用状态变量,如下所示:

class ViewModel: ObservableObject {
    @Published var faqs: [FAQ] = [
           FAQ(question: "What is the fastest animal on land?", answers: ["The cheetah"]),
           FAQ(question: "What colours are in a rainbox?", answers: ["Red", "Orange", "Yellow", "Blue", "Indigo", "Violet"])
       ]
}

并更新您的 ContentView:

struct ContentView: View {
    @ObservedObject var model = ViewModel()

    var body: some View {
        List {
            ForEach(model.faqs.indices) { index in
                Section(header: Text(self.model.faqs[index].question)
                    .onTapGesture {
                        self.model.faqs[index].isExpanded.toggle()
                    }
                ) {
                    if self.model.faqs[index].isExpanded {
                        ForEach(self.model.faqs[index].answers, id: \.self) {
                            Text("• " + [=11=]).font(.body)
                        }
                    }
                }
            }
        }
    }
}

ContentView 中的 @ObservedObject 属性 包装器现在监视其 ViewModel 对象宣布的任何更改,并且 @Published 包装器告诉 ViewModel class 宣布数组发生的任何事情,包括对其元素的更改。

通过将视图拆分为更小的独立视图,可以轻松实现这一点。

模型不需要知道答案是否显示在屏幕上。因此,无需查看数组即可知道视图是否展开。

此外,当其中一个部分展开时,不需要重新绘制整个列表。有关何时重绘视图的更多信息,请参阅 this article

struct FAQ {
    var question: String
    var answers: [String]
}

struct InfoView: View {
    let information: [FAQ] = [
        FAQ(question: "What is the fastest animal on land?", answers: ["The cheetah"]),
        FAQ(question: "What colours are in a rainbox?", answers: ["Red", "Orange", "Yellow", "Blue", "Indigo", "Violet"])
    ]

    var body: some View {
        List {
            ForEach(information, id: \.question) { info in
                InfoSection(info: info)
            }
        }
    }
}

struct InfoSection: View {

    let info: FAQ
    @State var showsAnswer = false

    var body: some View {
        Section(header: questionHeader) {
            if showsAnswer {
                ForEach(info.answers, id: \.self) { answer in
                    Text("• " + answer)
                }
            }
        }
    }

    var questionHeader: some View {
        Text(info.question)
            .foregroundColor(.primary)
            .onTapGesture {
            self.showsAnswer.toggle()
        }
    }
}