skip to Main Content

I am storing images in Core Data as binary data, and showing them in another view via a LazyVGrid to replicate a CollectionView in UIKit. However, performance seems to be really choppy/poor on scroll and was wondering if there’s any improvements that can be made.

I think it doesn’t like that I am creating images in the view, but I don’t see a reusableCell-type component in SwiftUI.

Here’s my existing code:

import Foundation
import SwiftUI

struct LibraryView: View {
    @Environment(.managedObjectContext) private var viewContext
    @FetchRequest(entity: Images.entity(), sortDescriptors: [], predicate: nil)

    private var images: FetchedResults<Images>
    private var threeColumnGrid = [GridItem(.flexible(minimum: 80)), GridItem(.flexible(minimum: 80)), GridItem(.flexible(minimum: 80))]


    var body: some View {
        ScrollView {
            LazyVGrid(columns: threeColumnGrid) {
                ForEach(self.images, id: .self) { fetchedImg in
                    GeometryReader { gr in
                    if let data = fetchedImg.image,
                       let image = UIImage(data: data) {
                        Image(uiImage: image)
                          .resizable()
                          .scaledToFill()
                          .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
                          .aspectRatio(1, contentMode: .fill)
                          .background(Color.gray)
                      }
                    }
                    .clipped()
                    .cornerRadius(10)
                    .aspectRatio(1, contentMode: .fit)
                }
            }
            .padding(5)
          }
    }
}

Any tips/suggestions would be greatly appreciated. Thanks!

2

Answers


  1. You need to break it up into smaller View data structs because yours is too big you are loading all the binary data and creating all the UIImages rather than taking advantage of the LazyVGrid‘s feature of calling body only on Views that are on screen. To resolve this simply move the code inside the ForEach into a custom View struct. If you now access the binary data in the child View’s body it is only loaded into the managed object when the row scrolls onto screen.

    Also, you can make a computed property on an NSManagedObject extension to cache the UIImage and clear it in the object’s didTurnIntoFault or the View’s onDisappear. You could also use a child context so that all of the objects and thus all of the cached UIImages are completely cleared when navigating away from the list when the child context is freed (usually it would be stored in an @State struct).

    Login or Signup to reply.
  2. Hope this can help others.

    Above code is fine: it’s correct using LazyVGrid inside ScrollView.
    When I refactored I reused the some code, but by mistake I used:

    ScrollView {
                MyCustomLazyVGrid....
    

    But in MyCustomLazyVGrid there was by mistake "ScrollView" : it does work for a small amount of cells, but for, say 1000 becomes unacceptable.

    So double check if in hierarchy by mistake you have multiple "ScrollView" nested.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search