SwiftUI Bug-列表更改锁定UI-(旧标题:SwiftUI核心数据提取非常慢)



更新#4

  • 我对这篇文章进行了重新排序,以便更容易阅读。你将在下面阅读的内容将详细介绍我在使用SwiftUI时遇到的一个错误。我最近请求苹果公司提供代码级别的支持,苹果公司也证实了这一点,并要求我寻求解决方案的反馈(也已经完成,但还没有回答)

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

我在苹果开发者论坛上看到过其他经历过这个bug的人。他们的临时解决方案是"将数组设置为空白",从而在修改列出的数据集之前完全清除列表约100毫秒。对于使用数据数组迭代List或ForEach的用户来说,这将充分避免锁定。

问题是,正如本文所述,使用CoreData时,似乎没有任何方法可以清除推送信件(获取请求)之间的列表。

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

任何关于解决方法的意见都将不胜感激。

更新#3

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

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

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

原始帖子

有什么想法吗?

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

下面的代码正在处理一个约有5000条记录的测试数据集。在搜索中,每次更改输入(键入每个字母),搜索都会再次运行,这会将系统拖入停顿状态(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"))
}
}
}

感谢您的帮助。

更新:

在编辑之前,我报告了存储数据的另一个问题,然而,这个问题通过下面的帖子得到了解决:

CoreData写入对象的速度较慢

更新#2:

我最初的问题是如何在我的提取中添加批次限制,看看这是否有帮助。我能够在不使用FetchRequest包装的情况下重写fetch,使用NSFetchRequest并添加了批限制。这对局势没有任何帮助。。

再次感谢

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

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

关键是,当NavigationLinklist中使用时,即使用户尚未导航到该视图,也会立即加载目标视图。为了克服这一点,必须使目的地视图变得懒惰。

在我的案例中,我在主视图中选择了一种食物,然后在详细视图中显示了所选食物的180多种特性的列表。。。

解决方案:

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

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

并像这样使用:

NavigationLink(destination: LazyView(Text("Detail Screen"))){
Text("Tap me to see detail screen!")
}

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

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

此外,苹果反馈仍然没有回复,甚至没有确认。我的代码级别支持请求被记入借方,尽管他们表示无法帮助我。我很不高兴。。

无论如何,如果你想构建一个感觉像是在泥中挖掘的应用程序,使用CoreData并能够搜索这些数据,下面是你的解决方法。。

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

struct MyDataModel: Hashable {
let title: String
let name: String
let myData: String
}

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

class MyData:ObservableObject {
@Published var searchDataArray = [MyDataModel]()
}

第三:确保将环境变量推送到计划使用的视图中,这是:(这个例子在我的SceneDelegate.swift文件中

let myData = MyData()

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

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

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

最后,从要搜索的视图中,您可以迭代已发布的env-var,并且可以在对搜索条件进行更改之间清除数组,并延迟以避免错误。

ForEach(self.myData.searchDataArray, id: .self) { fetchedItem in
Text(fetchedItem.name)
}

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

它真的很慢很可怕。这是有效的,但我认为我不能在这种混乱的情况下接近生产。

最新更新