SwiftUI 错误 - 列表更改锁定 UI -(旧标题:SwiftUI CoreData 获取非常慢)

Lka*_*abo 5 sqlite xcode core-data swiftui

更新 #4

  • 我重新排序了这篇文章,以便更轻松地阅读。您将在下面阅读的内容将详细介绍我在使用 SwiftUI 时遇到的错误。我最近请求 Apple 提供代码级别的支持,他确认了这一点,并要求我联系解决问题的反馈(也已完成,但尚未答复)。

错误是这样的:在 SwiftUI 视图中显示 List 或 ForEach 后,如果您通过更改列出的项目数来更改该视图,则 UI 在尝试计算已更改/需要更改的行数时会锁定。 .

我在 Apple 开发论坛中看到其他人遇到过这个错误。他们的临时解决方案是“将数组设置为空白”,从而在修改列出的数据集之前彻底清除列表大约 100 毫秒。这将充分避免用户使用数据数组迭代 List 或 ForEach 时的锁定。

问题是,对于 CoreData,按照本文中的描述使用,似乎没有任何方法可以清除推送的字母之间的列表(获取请求)。

在更新 #3 中,有一个 GitHub 项目显示了此问题的示例数据。

对解决方法的任何输入表示赞赏。

更新 #3

不好.. 正如这篇文章中所述,我能够从使用 CoreData 更改为本地 SQLite 数据库文件。我的结果是搜索和使用 CoreData 一样慢。我不知道这里发生了什么。但也许它与 SwiftUI 输出的渲染结果有关?无论哪种方式,搜索和显示大量数据似乎都是不可能的。

根据 J. Doe 的要求,我在 GitHub 上发布了一个示例项目,该项目演示了这个问题。这个项目可以在这里找到

我希望有人能看到我做错了什么。我很难相信这只是 iOS 的一个限制。

原帖

有任何想法吗?

我觉得我缺少一些基本的东西。我的获取请求(下面的代码)非常慢。我试图向 CoreData 模型添加一个索引,但有负面改进(来自下面的 J. Doe 的建议)。我想也许我需要以某种方式向 fetch 请求添加一个 fetchBatchSize 声明(想通了这一点 - 请参阅下面的更新 #2 - 没有帮助),但是使用 SwiftUI 中的属性包装器 @FetchRequest,似乎没有办法做这个。

下面的代码正在处理大约 5,000 条记录的测试数据集。在搜索中,每次更改输入(输入每个字母)时,搜索都会再次运行,这会拖累系统停止(CPU 100+% 和内存使用量增加)。

在以前的应用程序中,我完成了类似的任务,但这些应用程序使用 SQLite 数据文件并用 ObjC 编写。在这些情况下,事情真的很快,超过了这个测试数据集的 3 倍。

如果有人能指出我正确的方向来加速我的 CoreData 获取,我将不胜感激。如果我不需要,我不想回到 SQLite 文件。

非常感谢!

使用 SwiftUI,这是我的代码:

struct SearchView: View {


    @Binding var searchTerm:String
    var titleBar:String

    var fetch: FetchRequest<MyData>
    var records: FetchedResults<MyData>{fetch.wrappedValue}

    init(searchTerm:Binding<String>, titleBar:String) {
        self._searchTerm = searchTerm
        self.titleBar = titleBar
        self.fetch = FetchRequest(entity: MyData.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \ MyData.header, ascending: true)], predicate: NSCompoundPredicate(type: .and, subpredicates: [ NSCompoundPredicate(type: .or, subpredicates: [NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(MyData.title),searchTerm.wrappedValue), NSPredicate(format: "%K CONTAINS[cd] %@", #keyPath(MyData.details),searchTerm.wrappedValue)]), NSPredicate(format: "%K == %@", #keyPath(MyData.titleBar),titleBar)])) //fetch request contains logic for module and search data - need to fix sort order later
    }

    var body: some View {


        List{

            Section(header: SearchBar(text: $searchTerm)) {

                ForEach(records, id: \.self) { fetchedData in

                    VStack {
                        NavigationLink(destination: DetailView(titleBar: fetchedData.title!, statuteData: fetchedData.details!, isFavorite: fetchedData.isFavorite)) {

                            HStack {
                                Text(fetchedData.header!)
                                    .font(.subheadline)

                                VStack(alignment: .leading) {
                                    Text(fetchedData.title!)
                                }
                                .scaledToFit()

                                Spacer()

                                if fetchedData.isFavorite {
                                    Image(systemName: "star.fill")
                                        .imageScale(.small)
                                        .foregroundColor(.yellow)
                                }
                            }
                        }
                    }
                }
            }.navigationBarTitle(Text("Search"))
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

谢谢您的帮助。

更新:

在编辑之前,我报告了存储数据的另一个问题,但是,该问题已通过以下帖子解决:

CoreData 写入对象很慢

更新#2:

我最初的问题是问如何在我的 fetch 中添加批量限制以查看是否有帮助。我能够在不使用 FetchRequest 包装器的情况下重写获取,使用 NSFetchRequest 并添加了批处理限制。它对这种情况没有任何帮助..

再次感谢

use*_*284 5

我也遇到了同样的问题,即list从核心数据中获取结果时滚动速度非常慢。与我之前使用 UIKit 的解决方案相比,使用 swiftUI 的速度很慢(相同的数据,相同的获取)。除了必须使用 fetchOffset 和 fetchLimit 之外,我发现一个主要的性能问题是由于使用NavigationLink. 没有 NavigationLink 时,性能非常好,但使用 NavigationLink 时,性能就不那么好了。

\n

在寻找解决方案时,我发现了Anupam Chugh 撰写的这篇博文 ,他写了有关此问题的文章,并提供了我在下面复制的解决方案。我很感激他。这是他的解决方案,不是我的。

\n

关键点是,当NavigationLink在 中使用时list,即使用户尚未导航到该视图,也会立即加载目标视图。为了克服这个问题,我们必须让目的地视图变得懒惰。

\n

就我而言,我在主视图中选择了一种食物,然后在详细视图中显示了所选食物的 180 多个属性的列表......

\n

解决方案:

\n

使用以下代码创建一个新文件

\n
import SwiftUI\n\n/// Creates a lazy view from view.\n///\n/// Helpfull for use of `NavigationLink` within `list`, where destination views    are loaded immediately even when the user hasn\xe2\x80\x99t navigated to that view.\n/// Embedding the destination view in LazyView makes the destination view lazy and speeds up performance significantly for long lists.\n///\n/// ```Swift\n/// NavigationLink(destination: LazyView(Text("Detail Screen"))){\n///    Text("Tap me to see detail screen!")\n/// }\n/// ```\n///\n/// Source: [Blog post by Anupam Chugh on Medium]( https://medium.com/better-programming/swiftui-navigation-links-and-the-common-pitfalls-faced-505cbfd8029b). Thank you!!!!\nstruct LazyView<Content: View>: View {\n    let build: () -> Content\n        init(_ build: @autoclosure @escaping () -> Content) {\n        self.build = build\n    }\n    var body: Content {\n        build()\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

并像这样使用它:

\n
NavigationLink(destination: LazyView(Text("Detail Screen"))){\n   Text("Tap me to see detail screen!")\n}\n
Run Code Online (Sandbox Code Playgroud)\n

我希望这对其他人也有帮助。

\n


Lka*_*abo 1

这是解决方法,我可以补充一下,这是完全不可接受的。它使应用程序在技术上可以运行,但速度太慢,感觉就像在 8086 上加载 Windows 10 一样。荒谬。

此外,苹果反馈仍然没有答复,甚至没有确认。尽管他们表示无法帮助我,但我的代码级支持请求已被扣除。不开心..

不管怎样,如果你想构建一个感觉就像在泥土中挖掘的应用程序,使用 CoreData 并能够搜索该数据,这就是你的解决方法。

第一:创建数据的可哈希模型,或者至少是您需要搜索和/或显示的数据部分:

struct MyDataModel: Hashable {
  let title: String
  let name: String
  let myData: String
}
Run Code Online (Sandbox Code Playgroud)

第二:创建一个 ObservableObject 类,该类发布一个变量,其中包含刚刚创建的数据模型类的数组:

class MyData:ObservableObject {
  @Published var searchDataArray = [MyDataModel]()
}
Run Code Online (Sandbox Code Playgroud)

第三:确保将环境变量推送到您计划使用的视图:(此示例位于我的 SceneDelegate.swift 文件中

let myData = MyData()
Run Code Online (Sandbox Code Playgroud)

并附加到.environmentObject(myData)您需要它的任何视图中。

第四:从您的视图访问环境变量:@EnvironmentObject var myData: MyData并将获取结果加载到已发布的数据数组中,我使用此函数来完成任务:

func arrayFiller(){ 

    if self.myData.searchDataArray.count > 0 {
        self.myData.searchDataArray.removeAll()
    }

    for item in self.fetchRequest {
        self.myData.searchDataArray.append(MyDataModel(title: item.title!, name: item.name!, myData: item:myData!))
    }
}

Run Code Online (Sandbox Code Playgroud)

最后,从要搜索的视图中,您可以迭代已发布的环境变量,并且可以在搜索条件更改之间延迟清除数组,以避免出现错误。

ForEach(self.myData.searchDataArray, id: \.self) { fetchedItem in
    Text(fetchedItem.name)
}
Run Code Online (Sandbox Code Playgroud)

然后,我使用 an.onReceive来观察我的 searchTerm 变量的更改,擦除已发布的数组,等待 10 毫秒并使用与我的搜索词匹配的数据重新填充数组。

它真的很慢而且很可怕。它有效,但我认为我无法在这种混乱的情况下进行任何接近生产的工作。