我正在尝试使用存储在照片应用程序中的照片来实现 Gridview,以便用户可以选择一张照片并将其选为他的个人资料照片。在 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()
}
}
}