Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.0k views
in Technique[技术] by (71.8m points)

xcode - SwiftUI Bug - List changes lock UI - (OLD TITLE: SwiftUI CoreData fetch is very slow)

Update #4

  • I've Reordered this post to read a little more easily. What you will read below will detail a bug I've experienced using SwiftUI. I recently requested code level support from apple who confirmed same and asked that I reach out to feedback for resolution (which was also done, no answer yet).

The bug is this: After displaying a List or ForEach in a SwiftUI View, if you alter that view by changing the number of items listed, the UI locks up while it attempts to calculate the number of rows that have changed / need to change..

I have seen others who have experienced this bug in the Apple dev forums. Their temporary solution was to "set the array to blank" thereby clearing the list completely for about 100 milliseconds before modifying the listed dataset. This will avoid the lock up sufficiently for users iterating a List or ForEach using an Array of data.

Problem is, with CoreData, being used as is described in this post, there does not seem any way to clear the list in between letters being pushed (fetch requests).

In Update #3, there is a GitHub project which shows a sample of this issue with sample data.

Any input on workarounds is appreciated.

Update #3

Not good.. As described in this post, I was able to change from using CoreData to a local SQLite database file. My results were that the search was just as slow as using CoreData. I do not know what is going on here. But maybe it is something with rendering results to the SwiftUI output? Either way, searching and displaying a large amount of data seems impossible..

Ive posted a sample project which demonstrates this problem on GitHub, per J. Doe's request. This project can be found here

I hope someone can see what I'm doing wrong. I find it hard to believe that this is just a limitation of iOS..

Original Post

Any ideas?

I feel like I am missing something fundamental. My fetch request (code below) is super slow. I have tried to add an index to the CoreData model with negative improvement (suggestion from J. Doe below). I am thinking maybe I need to somehow add a fetchBatchSize declaration to the fetch request (figured this out - see update #2 below - no help), but with the property wrapper @FetchRequest in SwiftUI, there does not seem to be a way to do this.

The code below is working on a test dataset of about 5,000 records. In the search, each time the input is changed (with each letter typed), the search gets run again which drags the system to a halt (100+% on CPU and growing memory usage).

In previous apps, I have completed similar tasks, but those apps used an SQLite data file and were written in ObjC. In those instances, things were really fast, with more than 3 times this test dataset.

If anyone can point me in the right direction to speed up my CoreData fetch, I would be very appreciative. I do not want to have to go back to an SQLite file if I don't have to..

Thank you very much!

Using SwiftUI, here is my code:

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

Thank you for your assistance.

Update:

Before edit, I had reported another issue with storing data, however, that issue was resolved with the post below:

CoreData writing objects slow

Update #2:

My original question asked how to add the batch limit on my fetch to see if that helps. I was able to rewrite the fetch without using the FetchRequest wrapper, using NSFetchRequest and added the batch limit. It did nothing to help the situation..

Thanks again

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I had the same issue of very slow scrolling in a list with fetched results from core data. It was just slow using swiftUI compared to my previous solution using UIKit (same data, same fetch). Besides having to use fetchOffset and fetchLimit I found out that a major performance issue was due to using NavigationLink. Without NavigationLink the performance was great, but with the NavigationLink it was not.

Searching for a solution I found this blog post by Anupam Chugh who wrote about this and also provided a solution that I copied below. I am grateful to him. It's his solution, not mine.

The key point is that when NavigationLink is used within list, the destination views are loaded immediately, even when the user hasn't navigated to that view. To overcome this, one has to make the destination view lazy.

In my case I selected a food in the master view and then showed a list of more than 180 properties of the selected food in the detail view...

Solution:

Create a new file with the following code

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

and use it like this:

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

I hope this of help for others, too.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...