如何在 SwiftUI 中有效过滤长列表? [英] How do I efficiently filter a long list in SwiftUI?

查看:24
本文介绍了如何在 SwiftUI 中有效过滤长列表?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I've been writing my first SwiftUI application, which manages a book collection. It has a List of around 3,000 items, which loads and scrolls pretty efficiently. If use a toggle control to filter the list to show only the books I don't have the UI freezes for twenty to thirty seconds before updating, presumably because the UI thread is busy deciding whether to show each of the 3,000 cells or not.

在 SwiftUI 中是否有处理此类大列表更新的好方法?

Is there a good way to do handle updates to big lists like this in 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"))
    }

推荐答案

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

Have you tried passing a filtered array to the ForEach. Something like this:

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

更新

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

Update

As it turns out, it is indeed an ugly, ugly bug:

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

Instead of filtering the array, I just remove the ForEach all together when the switch is flipped, and replace it by a simple Text("Nothing") view. The result is the same, it takes 30 secs to do so!

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

解决方法

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

Workaround

I did find a workaround that works fast, but it requires some code refactoring. The "magic" happens by encapsulation. The workaround forces SwiftUI to discard the List completely, instead of removing one row at a time. It does so by using two separate lists in two separate encapsualted views: Filtered and NotFiltered. Below is a full demo with 3000 rows.

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

这篇关于如何在 SwiftUI 中有效过滤长列表?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆