问题描述
我正在尝试在 SwiftUI 中创建一个照片应用,以在设备上显示来自 PHAsset
的照片。
但是,当我不断滚动网格视图时,内存会不断增长并最终因内存问题而崩溃。
我也在从 RxSwfit 切换到 SwiftUI,所以如果有更好的方法来构建网格项及其视图模型,请提出建议。谢谢。
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)
}
}
}
}
解决方法
你说得对。 LazyVGrid
就像它的名字一样。一堆但懒惰。当我滚动时,我做了基本相同的布局和巨大的泄漏。为了缓解这种情况,我尝试将 UIImage
设置为在视图消失时进行设置。但看起来 Image
中的 View
消失后不会更新。所以泄漏是无法避免的。
我使用 20K 图像测试布局。从上到下滚动。只获取照片但不设置到 View
。 RAM 约为 30MB。但是在设置到 View
后获得约 800MB。
最后我回滚到 UICollectionView
。
这是我的布局。如果你有兴趣。