0

写真アプリに写真が保存されているGridviewを実装して、ユーザーが1枚の写真を選択してプロフィール写真として選択できるようにしようとしています。SwiftUI を使用する前は、コレクション ビューと Photos Kit を使用して画像を取得し、グリッドに表示していました。

SwiftUI に切り替えたので、LazyVGrid を使用してみました。ユーザーのすべての写真を取得して、グリッドに表示できます。ただし、大量のメモリを使用します。以前はメモリ リークがありましたが、Instruments でリークが表示されなくなりました。

ユーザーに見えなくなったときに、グリッドが表示された画像を実際にアンロードしていない可能性があると思いました。ただし、何度も上下にスクロールすると、以前よりも多くのメモリが使用されます。グリッドが常に新しいビューを作成しているように、古いビューは削除されません。LazyVGrid の原理を間違っているか誤解していますか?

class PhotoLibrary: ObservableObject {

    

    @Published var assets = [PHAsset]()

    @Published var imageCachingManager: PHCachingImageManager = PHCachingImageManager()

    func requestAuthorization() {

        PHPhotoLibrary.requestAuthorization { [weak self] (status) in

            guard let self = self else { return }



            switch status {

            case .authorized:

                self.getAllPhotos()

            case .denied:

                break

            case .notDetermined:

                break

            case .restricted:

                break

            case .limited:

                self.getAllPhotos()

            @unknown default:

                break

            }

        }

    }



    private func getAllPhotos() {

        

        imageCachingManager.allowsCachingHighQualityImages = false

        

        

        let allPhotosOptions = PHFetchOptions()

        allPhotosOptions.includeHiddenAssets = false


        allPhotosOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]

        

        let assets: PHFetchResult = PHAsset.fetchAssets(with: .image, options: allPhotosOptions)

        

        var _assets = [PHAsset]()

        

        assets.enumerateObjects({ asset, _, _ in

            _assets.append(asset)

        })

        

        DispatchQueue.main.async { [weak self] in

            self?.assets = _assets

        }

        

    }
    

    func resetCache() {

        imageCachingManager.stopCachingImagesForAllAssets()

        assets = []

    }

    

    func preCacheImages(assets: [PHAsset], size: CGSize) {

        DispatchQueue.global(qos: .userInitiated).async { [weak self] in

            guard let self = self else {

                return

            }

            

            let options = PHImageRequestOptions()

            options.deliveryMode = .opportunistic

            options.resizeMode = .fast

            options.isSynchronous = true

            options.isNetworkAccessAllowed = true

            

            self.imageCachingManager.startCachingImages(for: assets, targetSize: size, contentMode: .aspectFill, options: options)

        }

        

    }

    

    func fetchImage(index: Int, size: CGSize?) async -> UIImage? {

        

        guard let asset = assets[safe: index] else {

            return nil

        }

        

        let options = PHImageRequestOptions()

        options.deliveryMode = .opportunistic

        options.resizeMode = .fast

        options.isSynchronous = true

        options.isNetworkAccessAllowed = true

        

        return await withCheckedContinuation({

            [weak self] (continuation: CheckedContinuation<UIImage?, Never>) in

            

            guard let self = self else {

                return

            }

            

            self.imageCachingManager.requestImage(for: asset, targetSize: size ?? PHImageManagerMaximumSize, contentMode: .aspectFill, options: options) {(image, info) in

                continuation.resume(returning: image)

            }

        })

    }

    

    func removeFromCache(index: Int, size: CGSize) {

        DispatchQueue.global(qos: .userInitiated).async { [weak self] in

            

            guard let self = self else {

                return

            }

            

            guard let asset = self.assets[safe: index] else {

                return

            }

            

            let options = PHImageRequestOptions()

            options.deliveryMode = .opportunistic

            options.resizeMode = .fast

            options.isSynchronous = true

            options.isNetworkAccessAllowed = true

            

            self.imageCachingManager.stopCachingImages(for: [asset], targetSize: size, contentMode: .aspectFill, options: options)

        }

    }

    

}
struct PhotoView: View {

    var index: Int

    let thumbnailSize: CGSize?

    weak var photoLibrary: PhotoLibrary?

    @State var image: UIImage? = nil

    

    var body: some View {

        HStack {

            if let image = image {

                Image(uiImage: image)

                    .resizable()

                    .square()

            } else {

                Color.label

                    .aspectRatio(1.0, contentMode: .fill)

            }

        }

        .onDisappear(perform: {

            unload()

        })

        .onAppear(perform: {

            Task {

                guard let photoLibrary = photoLibrary else {

                    return

                }



                let image = await photoLibrary.fetchImage(index: index, size: thumbnailSize ?? PHImageManagerMaximumSize)

                

                DispatchQueue.main.async {

                    self.image = image

                }

            }

        })

    }

    

    private func unload() {

        self.image = nil

    }

}
struct PickProfilePictureView: View {
    
    @ObservedObject var photoLibrary = PhotoLibrary()
    
    var body: some View {
        VStack(alignment: .leading) {
        
            GeometryReader { reader in
                let width = reader.size.width / 4 - 6
                ScrollView {
                    
                    LazyVGrid(columns: [GridItem(), GridItem(), GridItem(), GridItem()]) {
                        ForEach(photoLibrary.assets.indices, id: \.self) { index in
                            
                            PhotoView(index: index, thumbnailSize: CGSize(width: width, height: width), photoLibrary: photoLibrary)
                                .contentShape(Rectangle())
                                .onDisappear(perform: {
                                    photoLibrary.removeFromCache(index: index, size: CGSize(width: width, height: width))
                                })
                        }
                    }
                }
                
            }
            
            Spacer()
        }
        .ignoresSafeArea(.container, edges: .bottom)
        .onAppear {
            self.photoLibrary.requestAuthorization()
        }
    }
}
4

0 に答える 0