Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/variables/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
如何在SwiftUI中高效地筛选长列表?_Swiftui_Swiftui List - Fatal编程技术网

如何在SwiftUI中高效地筛选长列表?

如何在SwiftUI中高效地筛选长列表?,swiftui,swiftui-list,Swiftui,Swiftui List,我一直在编写我的第一个SwiftUI应用程序,它管理一个图书收藏。它有一个大约3000个项目的列表,可以非常高效地加载和滚动。如果使用切换控件过滤列表以仅显示我没有的书籍,则UI在更新前会冻结二十到三十秒,可能是因为UI线程正忙于决定是否显示3000个单元格中的每一个 在SwiftUI中有没有一个好方法来处理这样的大列表的更新 var主体:一些视图{ 导航视图{ 名单{ 切换(isOn:$userData.showWantsOnly){ 文本(“显示需要”) } ForEach(userData

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

在SwiftUI中有没有一个好方法来处理这样的大列表的更新

var主体:一些视图{
导航视图{
名单{
切换(isOn:$userData.showWantsOnly){
文本(“显示需要”)
}
ForEach(userData.bookList){book in
if!self.userData.showWantsOnly | |!book.own{
导航链接(目的地:图书详细信息(图书:图书)){
书行(书:书)
}
}
}
}
}.navigationBarTitle(文本(“书籍”))
}

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

ForEach(userData.bookList.filter{return!$0.own}){book-in
导航链接(目的地:BookDetail(book:book)){BookRow(book:book)}
}
更新 事实证明,它确实是一个丑陋的虫子:

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

struct SwiftUIView:视图{
@环境对象变量userData:userData
@状态私有变量显示=false
var body:一些观点{
导航视图{
名单{
切换(isOn:$userData.showWantsOnly){
文本(“显示需要”)
}
如果self.userData.showWantsOnly{
文本(“无”)
}否则{
ForEach(userData.bookList){book in
导航链接(目的地:图书详细信息(图书:图书)){
书行(书:书)
}
}
}
}
}.navigationBarTitle(文本(“书籍”))
}
}
解决方法 我确实找到了一个工作速度很快的解决方法,但它需要一些代码重构。“魔法”是通过封装实现的。该解决方法强制SwiftUI完全放弃列表,而不是一次删除一行。它是通过在两个单独的封装视图中使用两个单独的列表来实现的:
Filtered
NotFiltered
。下面是3000行的完整演示

导入快捷界面
类UserData:observeObject{
@已发布的var showWantsOnly=false
@已发布的var图书列表:[图书]=[]
init(){

对于0中的uu..我认为我们必须等到SwiftUI列表性能在随后的测试版中得到改善。当列表从一个非常大的数组(500+)中筛选时,我也经历过同样的延迟我创建了一个简单的测试应用程序,用于对带有整数ID的简单数组和带有按钮的字符串的布局进行计时,以简单地更改呈现的数组-相同的延迟。

如果您在“ScenedLegate”文件中初始化类,则此代码将正常工作,如下所示:

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?
var userData = UserData()


func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Create the SwiftUI view that provides the window contents.
    let contentView = ContentView()

    // Use a UIHostingController as window root view controller.
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView:
            contentView
            .environmentObject(userData)
        )
        self.window = window
        window.makeKeyAndVisible()
    }
}

与复杂的解决方法不同,只需清空列表数组,然后设置新的过滤器数组。可能需要引入延迟,以便在随后的写入操作中不会忽略清空列表数组

List(listArray){item in
  ...
}
检查这篇文章

简而言之,本文提出的解决方案是将.id(UUID())添加到列表中:

列表(项目,id:\.self){
文本(“项目\($0)”)
}
.id(UUID())

“现在,像这样使用id()有一个缺点:您的更新不会动画化。请记住,我们实际上是在告诉SwiftUI旧列表已经消失,现在有了一个新列表,这意味着它不会尝试以动画方式移动行。”

在寻找如何使Seitenwerk的响应适应我的解决方案时,我发现了一个绑定扩展,它对我帮助很大。下面是代码:

struct ContactsView: View {
    
    @State var stext : String = ""
    @State var users : [MockUser] = []
    @State var filtered : [MockUser] = []
    
    var body: some View {
        
        Form{
            SearchBar(text: $stext.didSet(execute: { (response) in
                
                if response != "" {
                    self.filtered = []
                    self.filtered = self.users.filter{$0.name.lowercased().hasPrefix(response.lowercased()) || response == ""}
                }
                else {
                    self.filtered = self.users
                }
            }), placeholder: "Buscar Contactos")
            
            List{
             ForEach(filtered, id: \.id){ user in
                    
                    NavigationLink(destination: LazyView( DetailView(user: user) )) {
                        ContactCell(user: user)
                    }
                }
            }
        }            
        .onAppear {
                self.users = LoadUserData()
                self.filtered = self.users
        }
    }
}
这是绑定扩展:

extension Binding {
    /// Execute block when value is changed.
    ///
    /// Example:
    ///
    ///     Slider(value: $amount.didSet { print($0) }, in: 0...10)
    func didSet(execute: @escaping (Value) ->Void) -> Binding {
        return Binding(
            get: {
                return self.wrappedValue
            },
            set: {
                execute($0)
                self.wrappedValue = $0
            }
        )
    }
}
LazyView是可选的,但我费劲地展示了它,因为它对列表的性能有很大帮助,并且阻止swiftUI创建整个列表的NavigationLink目标内容

struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}
struct LazyView:View{
让我们构建:()->内容
init(uu构建:@autoclosure@escaping()->Content){
self.build=build
}
变量主体:内容{
构建()
}
}

我现在已经尝试过了。它可以快速呈现初始列表,但是如果我将
!$0.own
绑定到切换状态,我也会遇到同样的性能问题。信不信由你,这个错误的原因与类似Android的列表取消/初始化有关。不要再震惊了:)是的,谢谢,我记得了!我就是这样想出这个错误的他提出了一个解决方案。通过完全销毁列表,而不是让它缓存行以供以后使用。如果您有更复杂的筛选器以使生成的列表是动态的,这也有效吗?我认为您是对的。我相信这个问题很快会得到解决。您将代码段放在哪里以清空列表?只要您需要它的位置?例如,当您想在代码中的某个位置更新或添加元素时,您复制列表,然后根据需要修改它,然后使用上面截取的内容清空原始列表,使其刷新。在短暂的延迟后,您再次添加更新的列表。我尝试了此解决方案,但每次导航回f时都会崩溃从一个视图中,我有一个列表和.id(UUID())解决方案。我有同样的问题@sabiland,你找到解决方案了吗?
struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    var body: Content {
        build()
    }
}