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
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 theUIImage
s rather than taking advantage of theLazyVGrid
‘s feature of calling body only on Views that are on screen. To resolve this simply move the code inside theForEach
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’sdidTurnIntoFault
or the View’sonDisappear
. 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).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:
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.