【问题标题】:Expandable List in SwiftUI only expands once and won't contractSwiftUI 中的 Expandable List 只展开一次,不会收缩
【发布时间】:2020-04-09 00:46:06
【问题描述】:

我正在尝试(但失败了!)在 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("• " + $0).font(.body)
                        }
                    }
                }
            }
        }
    }
}

点击任何问题成功将答案展开到视图中,但再次点击同一标题不会缩小答案,点击第二个问题也不会扩大这些答案。

通过明智地放置prints,我可以看到isExpanded 在点击的第一个问题上切换为true,但随后不会切换回false

谁能解释我做错了什么?

【问题讨论】:

    标签: swiftui swiftui-list


    【解决方案1】:

    问题在于您的@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("• " + $0).font(.body)
                            }
                        }
                    }
                }
            }
        }
    }
    

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

    【讨论】:

    • @AshleyMills 我更新了我的答案以迭代 model.faqs.indices 而不是 model.faqs。在您的原始答案中,您查看了两次元素,一次在 ForEach 中,一次在 firstIndex(of:) 中,使您的代码在 O(n^2) 时间内运行。对索引进行迭代会使代码更加冗长,但如果数组中有很多常见问题解答,则效率会更高。
    【解决方案2】:

    通过将视图拆分为较小的自包含视图,可以轻松实现这一点。

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

    此外,当某个部分展开时,不需要重新绘制整个列表。有关视图何时重绘的更多信息,请参阅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()
            }
        }
    }
    
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-02-26
      • 2018-05-19
      • 2020-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-21
      • 1970-01-01
      相关资源
      最近更新 更多