UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Infinite / Lazy Loading + Core Data

Forums > SwiftUI

I am having a really hard time understanding how to implement infinite / lazy loading with Core Data in a SwiftUI project and am looking for help.

I've used the brilliant tutorials -- both here on hackingwithswift.com and from Core Data Mastery in SwiftUI from bigmountainstudio.com -- to build my Core Data store and views to display its data. But I'm confused about how a List or a ScrollView should "grab" the next "batch" of records when needed.

Here's the scenario, and I freely admit that I may be completely misunderstanding everything and am more than open to any input, advice, tips, tricks, or solutions anyone can provide:

When my app is installed, its Core Data store is already populated with (hypothetically) 1,000 records. Each record is displayed in a card in a List through which the user can scroll. But certainly I'm not going to load all 1,000 records at once, because it would take a long time and consume too much memory, yadda yadda yadda. So my FetchRequest only pulls the first 10 records. (Or any other reasonable number smaller than 100% of the records available.)

How do I trigger fetching the "next" batch of records when/if the user has scrolled to the end of the List? And, ideally, how could I trigger the List to grab the next batch once the 6th or 7th record has appeared so there isn't a delay when the 10th scrolls into view? And, further, do I need to worry about my app consuming too much memory once records 1 - 7 have scrolled out of view and I'm loading another batch (or two or three)? What happens if a user is viewing the 12th record and wants to scroll the other way back to view the 4th record?

2      

Hi David! But do you experience any performance issues or this is just hypothetical question? Better idea to check the performance with fake data and using profiling first. Also you might think about using @FetchedRequest property, which produces a value of type FetchedResults. This structure takes care of loading into context only the objects that are required by the view at any given moment. So using it might be more beneficial in some circumstances.

2      

@Hatsushira Thanks for the link, but that is for a UIKit implemetation. (I've actually used this code as a reference for several of my UIKit apps several years ago and know it well by now!) I'm specifically trying to find a SwiftUI explanation. i.e. What would be analogous in SwiftUI to the loadSavedData() function and where would it go in a view? I feel like I'm missing a fundamental concept and it's very frustrating.

@ygeras You're right in that for 1,000 records there shouldn't be any problem with performance. But the actual app will have tens of thousands of records, so it's definitely an issue to address now. And I am using @FetchRequest, actually. Here's some pseudo code:

struct TestView: View {

    @FetchRequest<PersonEntity>(sortDescriptors: [SortDescriptor(\PersonEntity.personid)])
    private var people

    var body: some View {

         List(people){ person in
             VStack{
                 PersonTitleView( title: person.viewName )
                 CardView(image1: person.viewImageOne, image2: person.viewImageTwo, favorite: person.viewAnswer, color: person.viewColor)
             }
         }
    }
}

This is where I'm saying I think I might be fundamentally misunderstanding something here. In SwiftUI -- unlike UIKit -- there's no place to "trigger" anything like loadSavedData() because ... well, because there isn't. Right? So how does that work?

2      

Well, personally I could not find any resource that explains in detail how @FetchedRequest works under the hood. So I rely on books that I've read that say it shouldn't be an issue even for really huge amount of data. However, did not handle such situation in practice myself, so relying on words from books :). As I mentioned above it will handle in the way that shouldn't create any problems.

“A fetch request loads all the objects available in the Persistent Store. This is not a problem when the number of objects is not significant. But a Persistent Store can manage thousands of objects, which can consume resources that the app and the system need to run. Therefore, instead of the fetch request, the @FetchRequest property wrapper produces a value of type FetchedResults. This is a structure that takes care of loading into the context only the objects that are required by the view at any given moment.”

Excerpt From SwiftUI for Masterminds 3rd Edition 2022 J.D. Gauchat

The issue might arise if you need to filter the data. But if you have Core Data Mastery in SwiftUI, you can use tips from Fetch Performance Tips section.

2      

You can use a NSFetchedResultsController with SwiftUI as well. Actually, Paul uses one in his UltimatePortfolioApp. If @FetchRequest handles this for you (I don't know the specifics, either) then this shouldn't be a problem. If not you can still try the way with NSFetchedResultsController.

2      

If you search this forum, @DelawareMathGuy made some helpful posts about @FetchRequest vs. NSFetchedResultsController.

2      

Hi @DavidGagne ,

It is possible to achieve your goal by implementing a RandomAccessCollection and using fetchLimit and fetchOffset. However, it should be noted that if your application uses Core Data with CloudKit, this method of loading data in small batches could potentially miss newly imported data in the background.

I do not have a ready-made example at the moment, but perhaps you can understand how to continuously retrieve data by customizing a type that conforms to RandomAccessCollection from the code snippet below. https://github.com/fatbobman/MovieHunter/blob/main/MovieHunter/Model/MovieGalleryLoader.swift

For more information on this project, you can refer to the article Building Adaptable SwiftUI Applications for Multiple Platforms .

I recommend that you do not try to use the method of fetching data in small batches unless you really encounter performance bottlenecks. As for how FetchRequest works, I also explained it in another article SwiftUI and Core Data - Data Fetching , but it has not been translated into English yet. (Due to domain restrictions, I am unable to write the original URL in my reply)

If you do encounter performance issues with Core Data in SwiftUI, you can try some optimization methods. In this article Memory Optimization Journey for a SwiftUI + Core Data App, I gave a few examples.

3      

Hi David. As a disclaimer, I will admit to only having worked with Core Data for 9 days (!). So hopefully I am not taking you down a wrong path. However, I have had the same performance concerns with the app I am currently developing, and have just moved from using Realm to Core Data. (It took me 7 days to port it, which included the time to learn how to use Core Data with SwiftUI.) So I've done quite a bit of research on this very topic recently.

It is my understanding that the @FetchRequest property wrapper (with its accompanying FetchedResults struct) provides the equivalent capabilities to NSFetchedResultsController as far as performance and lazy loading is concerned, in particular when used in conjunction with a List view (SwiftUI Lists are loaded lazily too).

I suggest creating a testbed database of a very large number of records, and verifying this for yourself. But I think you'll find that SwiftUI + Core Data + @FetchRequest + List already providing the capabilities you're looking for (without having to do any heroic processing of your own).

4      

Hacking with Swift is sponsored by RevenueCat

SPONSORED Take the pain out of configuring and testing your paywalls. RevenueCat's Paywalls allow you to remotely configure your entire paywall view without any code changes or app updates.

Learn more here

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.