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