如何在 SwiftUI 中有效过滤长列表?

Eif*_*ion 3 swiftui swiftui-list

我一直在编写我的第一个 SwiftUI 应用程序,它管理一个图书收藏。它有List大约 3,000 个项目,可以非常有效地加载和滚动。如果使用切换控件过滤列表以仅显示我没有的书籍,则 UI 在更新前会冻结 20 到 30 秒,大概是因为 UI 线程正忙于决定是否显示 3,000 个单元格中的每一个。

在 SwiftUI 中,有没有一种很好的方法来处理像这样的大列表的更新?

var body: some View {
        NavigationView {
            List {
                Toggle(isOn: $userData.showWantsOnly) {
                    Text("Show wants")
                }

                ForEach(userData.bookList) { book in
                    if !self.userData.showWantsOnly || !book.own {
                        NavigationLink(destination: BookDetail(book: book)) {
                            BookRow(book: book)
                        }
                    }
                }
            }
        }.navigationBarTitle(Text("Books"))
    }
Run Code Online (Sandbox Code Playgroud)

kon*_*iki 7

您是否尝试过将过滤后的数组传递给 ForEach。像这样的东西:

ForEach(userData.bookList.filter {  return !$0.own }) { book in
    NavigationLink(destination: BookDetail(book: book)) { BookRow(book: book) }
}
Run Code Online (Sandbox Code Playgroud)

更新

事实证明,它确实是一个丑陋的、丑陋的错误:

我没有过滤数组,而是在开关翻转时将 ForEach 全部删除,并用一个简单的Text("Nothing")视图替换它。结果是一样的,这样做需要30秒!

struct SwiftUIView: View {
    @EnvironmentObject var userData: UserData
    @State private var show = false

    var body: some View {
        NavigationView {

            List {
                Toggle(isOn: $userData.showWantsOnly) {
                    Text("Show wants")
                }

                if self.userData.showWantsOnly {
                   Text("Nothing")
                } else {
                    ForEach(userData.bookList) { book in
                        NavigationLink(destination: BookDetail(book: book)) {
                            BookRow(book: book)
                        }
                    }
                }
            }
        }.navigationBarTitle(Text("Books"))
    }
}
Run Code Online (Sandbox Code Playgroud)

解决方法

我确实找到了一种快速运行的解决方法,但它需要一些代码重构。“魔法”通过封装发生。该解决方法强制 SwiftUI 完全丢弃列表,而不是一次删除一行。它通过在两个单独的封装视图中使用两个单独的列表来实现:FilteredNotFiltered. 下面是一个包含 3000 行的完整演示。

import SwiftUI

class UserData: ObservableObject {
    @Published var showWantsOnly = false
    @Published var bookList: [Book] = []

    init() {
        for _ in 0..<3001 {
            bookList.append(Book())
        }
    }
}

struct SwiftUIView: View {
    @EnvironmentObject var userData: UserData
    @State private var show = false

    var body: some View {
        NavigationView {

            VStack {
                Toggle(isOn: $userData.showWantsOnly) {
                    Text("Show wants")
                }

                if userData.showWantsOnly {
                    Filtered()
                } else {
                    NotFiltered()
                }
            }

        }.navigationBarTitle(Text("Books"))
    }
}

struct Filtered: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        List(userData.bookList.filter { $0.own }) { book in
            NavigationLink(destination: BookDetail(book: book)) {
                BookRow(book: book)
            }
        }
    }
}

struct NotFiltered: View {
    @EnvironmentObject var userData: UserData

    var body: some View {
        List(userData.bookList) { book in
            NavigationLink(destination: BookDetail(book: book)) {
                BookRow(book: book)
            }
        }
    }
}

struct Book: Identifiable {
    let id = UUID()
    let own = Bool.random()
}

struct BookRow: View {
    let book: Book

    var body: some View {
        Text("\(String(book.own)) \(book.id)")
    }
}

struct BookDetail: View {
    let book: Book

    var body: some View {
        Text("Detail for \(book.id)")
    }
}
Run Code Online (Sandbox Code Playgroud)