Reputation: 612
I'm trying to create a photo app in SwiftUI that display the photos from PHAsset
on a device.
However when I keep scrolling the grid view, the memory would keep growing and eventually crash because of memory issue.
I'm also switching from RxSwfit to SwiftUI, so if there's a better way to construct grid items and their view models, please do suggest. Thank you.
struct LibraryView: View {
@ObservedObject var viewModel = ViewModel(photoLibraryService: PhotoLibraryService.shared)
private var gridItemLayout = Array(repeating: GridItem(.flexible(), spacing: 2), count: 4)
var body: some View {
GeometryReader { gp in
let width = CGFloat((Int(gp.size.width) - (2 * 3)) / 4)
ScrollView {
LazyVGrid(columns: gridItemLayout, spacing: 2) {
ForEach(viewModel.assets) { asset in
PhotoGridItem(asset: asset)
.frame(width: width, height: width)
.clipped()
}
}
}
}
}
}
struct PhotoGridItem: View {
@ObservedObject var viewModel: ViewModel
init(asset: LibraryAsset) {
viewModel = ViewModel(asset: asset)
}
var body: some View {
Image(uiImage: viewModel.image)
.resizable()
.scaledToFill()
.clipped()
.onAppear {
viewModel.fetchImage()
print("onAppear called")
}.onDisappear {
viewModel.cancelRequest()
print("onDisappear called")
}
}
}
extension PhotoGridItem {
class ViewModel: ObservableObject {
@Published var image: UIImage = UIImage()
var imageRequestID: PHImageRequestID?
let asset: LibraryAsset
init(asset: LibraryAsset) {
self.asset = asset
}
// Note that the completion block below would be called multiple times
// At first a smaller image would get returned first
// Then it would return a clear image
// Like it's progressively loading the image
func fetchImage() {
DispatchQueue.global(qos: .userInteractive).async {
let requestOptions = PHImageRequestOptions()
requestOptions.isNetworkAccessAllowed = true
requestOptions.deliveryMode = .opportunistic
PHImageManager.default().requestImage(
for: self.asset.asset,
targetSize: CGSize(width: 300, height: 300),
contentMode: .aspectFill,
options: requestOptions,
resultHandler: { [weak self] (image: UIImage?, info: [AnyHashable: Any]?) -> Void in
if let imageRequestID = info?[PHImageResultRequestIDKey] as? PHImageRequestID {
self?.imageRequestID = imageRequestID
}
DispatchQueue.main.async {
if let image = image {
self?.image = image
}
}
}
)
}
}
func cancelRequest() {
image = UIImage()
if let imageRequestID = imageRequestID {
PHImageManager.default().cancelImageRequest(imageRequestID)
}
}
}
}
Upvotes: 2
Views: 921
Reputation: 987
You are right. The LazyVGrid
is works likes it's name. A stack but lazy. I do basically the same layout and huge leaking when I scrolling. To ease that, I try to set UIImage
to set when the view disappear. But looks like the Image
in the View
does not update after it's disappeared. So the leaking can not avoid.
I test the layout with 20K images. Scroll from top to bottom. Just fetch photo but not set into the View
. The RAM is ~30MB. But get ~800MB after set into the View
.
Finally I rollback to UICollectionView
.
That's my layout. If you have interest.
Upvotes: 2