显示媒体文件时 SwiftUI 的 LazyVGrid 的内存分配问题

问题描述 投票:0回答:1

我目前正在开发一个 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)
,但这两种方法都不起作用。我还尝试将数据分成多个部分,并按照 @baronfacanother thread 中的建议创建包含多个图像的行。然而,@baronfac的建议只是将内存问题从
n
优化到
n/3
,这种线性减少并没有真正改变问题的内存复杂性,因为它仍然是
O(n)

有没有一种简单直观的方法来处理 LazyVGrid 没有内存释放的问题?如果 UIKit 实现是最好的方法,有人可以帮助确定我当前的 UIKit 代码有什么问题导致 GalleryView 显示空白屏幕吗?

ios swift swiftui uikit lazyvgrid
1个回答
0
投票
  1. 为什么不使用
    UIViewRepresentable
    代替视图控制器变体,并使用 SwiftUI 作为父导航视图,在其中注入 Collection 视图 (
    UIView
    )?
  2. 内存管理问题在这里很广泛,假设只有您的集合视图的单元格将保存
    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)
}

现在,只要视图可见,资源就会被加载并保留在内存中。

© www.soinside.com 2019 - 2024. All rights reserved.