Henry Ngan
Henry Ngan

Reputation: 612

SwiftUI Grid Item Memory Leak

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

Answers (1)

user25917
user25917

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.

https://github.com/MainasuK/JustPhotoCompress/blob/786b4bc1282747bdbb6c7e37f86a7f930c2044e6/PhotoCompress/Scene/PhotoGallery/View/PhotoThumbnailView.swift

Upvotes: 2

Related Questions