我目前正在开发一个 SwiftUI 应用程序,该应用程序在
GalleryView
上使用 LazyVGrid 在网格中显示媒体文件,其中每行将有 3 个媒体文件。媒体文件是从应用程序的缓存加载的,而不是从用户的 Photos
应用程序加载。当用户向下滚动查看更多媒体数据时,媒体文件会延迟加载到内存中,并且隐藏在视图中的媒体文件不会从内存中释放,因为 LazyVGrid 没有内置的内存释放机制。当存在大量图像或视频要在 n>10
上显示时,内存问题会变得更加明显(n
,其中 GalleryView
是媒体文件的数量):
struct GalleryView: View {
@Binding var mediaFiles: [Media]
var body: some View {
GeometryReader { geometry in
ScrollView {
let width = geometry.size.width / 3 - 10
let gridItemLayout = [GridItem(.fixed(width)), GridItem(.fixed(width)), GridItem(.fixed(width))]
LazyVGrid(columns: gridItemLayout, spacing: 10) {
ForEach(mediaFiles, id: \.id) { media in
if let image = media.image {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(width: width)
} else if let videoURL = media.videoURL {
// Display video thumbnail here
}
}
}
}
}
}
}
struct Media: Identifiable {
let id = UUID()
let creationTime: Date
var image: UIImage?
var videoURL: URL?
}
我尝试了自定义 UIKit 方法来处理内存释放问题:
struct CollectionView: UIViewControllerRepresentable {
typealias UIViewControllerType = UICollectionViewController
@Binding var mediaFiles: [Media]
var width: CGFloat
func makeUIViewController(context: Context) -> UICollectionViewController {
let layout = UICollectionViewFlowLayout()
layout.itemSize = CGSize(width: width / 3, height: width / 3)
layout.minimumLineSpacing = 10
layout.minimumInteritemSpacing = 10
let collectionViewController = UICollectionViewController(collectionViewLayout: layout)
collectionViewController.collectionView?.register(MediaCell.self, forCellWithReuseIdentifier: "cell")
collectionViewController.collectionView?.backgroundColor = .white
return collectionViewController
}
func updateUIViewController(_ uiViewController: UICollectionViewController, context: Context) {
uiViewController.collectionView?.dataSource = context.coordinator
uiViewController.collectionView?.delegate = context.coordinator
uiViewController.collectionView?.reloadData()
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate {
var parent: CollectionView
init(_ parent: CollectionView) {
self.parent = parent
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return parent.mediaFiles.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MediaCell
let media = parent.mediaFiles[indexPath.row]
if let image = media.image {
cell.imageView.image = image
}
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
// Handle selection here
}
}
}
这样我就可以使用
CollectionView
代替 GalleryView
中的 LazyVGrid,如下所示:
CollectionView(mediaFiles: $mediaFiles, width: width)
.frame(width: geometry.size.width, height: geometry.size.height)
.onAppear {
model.loadMediaFiles()
if let lastMediaIndex = mediaFiles.indices.last {
scrollView.scrollTo(lastMediaIndex, anchor: .bottom)
}
}
但是自定义 UIKit 代码未按预期工作,因为
GalleryView
现在显示空白屏幕(在亮/暗模式下),而不是每行显示 3 个媒体项目。我还尝试过使用 List 代替 LazyVGrid,或者尝试使用 Chris C 的代码将问题的内存复杂性优化为 O(1)
,但这两种方法都不起作用。我还尝试将数据分成多个部分,并按照 @baronfac 的 another thread 中的建议创建包含多个图像的行。然而,@baronfac的建议只是将内存问题从n
优化到n/3
,这种线性减少并没有真正改变问题的内存复杂性,因为它仍然是O(n)
。
有没有一种简单直观的方法来处理 LazyVGrid 没有内存释放的问题?如果 UIKit 实现是最好的方法,有人可以帮助确定我当前的 UIKit 代码有什么问题导致 GalleryView 显示空白屏幕吗?
UIViewRepresentable
代替视图控制器变体,并使用 SwiftUI 作为父导航视图,在其中注入 Collection 视图 (UIView
)?AVAsset
,UIImage
,PHAsset
,导致每次重新使用新单元格时它们都会被覆盖。通过此设置,您将只有 N 个实时资产实例,其中 N = 可见单元格的数量。现在要在 SwiftUI 中执行此操作,您可以使用 onAppear(perform:) 和 onDisappear(perform:) 视图修饰符。
这是我会尝试的方法:
struct GalleryItemView: View {
@Binding var media: Media
@State var asset: AVAsset? = nil
func loadAssetIfNeeded() {
guard self.asset == nil else { return nil }
guard let videoURL = media.videoURL else { return }
self.asset = AVAsset(url: videoURL)
}
func flushAsset() {
self.asset = nil
}
var body: some View {
VStack {
if let image = media.image {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(width: width)
} else if let asset = self.asset {
SomeVideoThumbnailView(previwing: asset)
}
}
.onAppear(perform: {
self.loadAssetIfNeeded()
})
.onDisappear {
self.flushAsset()
}
}
}
然后只需在您的
LazyVGrid
的 ForEach
中替换此视图,例如:
ForEach($mediaFiles) { $media in
GalleryItemView(media: $media)
}
现在,只要视图可见,资源就会被加载并保留在内存中。