SwiftUI中如何修改代码同时访问相册的图片与视频?
问题:相册清理应用如何同时访问图片和视频资产
我正在开发一款相册清理应用,但在访问设备相册中的视频时遇到了瓶颈。以下是我用于访问和编辑图片的代码:
import Foundation import Photos class PhotosModel: ObservableObject { @Published var errorString : String = "" @Published var photoArray: [BetterUIImage] = [] private var currentIndex = 0 private var totalInitialPhotos = 0 private var photosToDelete = [PHAsset]() init() { PHPhotoLibrary.requestAuthorization { (status) in DispatchQueue.main.async { switch status { case .authorized, .limited: self.errorString = "" self.getInitialPhotos() case .denied, .restricted: self.errorString = "Photo access permission denied" case .notDetermined: self.errorString = "Photo access permission not determined" @unknown default: fatalError() } } } } fileprivate func getInitialPhotos() { let manager = PHImageManager.default() let requestOptions = PHImageRequestOptions() requestOptions.isSynchronous = false requestOptions.deliveryMode = .highQualityFormat let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] let results: PHFetchResult = PHAsset.fetchAssets(with: fetchOptions) totalInitialPhotos = results.count if results.count > 2 { for i in 0..<3 { let asset = results.object(at: i) let size = CGSize(width: 700, height: 700) //You can change size here manager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { (image, _) in DispatchQueue.main.async { if let image = image { let betterImage = BetterUIImage(image: image, asset: asset) self.photoArray.append(betterImage) } else { print("error asset to image") } } } } currentIndex = 3 } else if results.count > 0 { for i in 0..<results.count { let asset = results.object(at: i) let size = CGSize(width: 700, height: 700) //You can change size here manager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { (image, _) in DispatchQueue.main.async { if let image = image { let betterImage = BetterUIImage(image: image, asset: asset) self.photoArray.append(betterImage) } else { print("error asset to image") } } } } } else { DispatchQueue.main.async { self.errorString = "No photos to display" } } self.photoArray = self.photoArray.reversed() print("getAllPhotos() completed") } func appendToPhotosToDelete(_ asset: PHAsset) { photosToDelete.append(asset) print("appendToPhotosToDelete() completed") } fileprivate func getNextPhoto() { let manager = PHImageManager.default() let requestOptions = PHImageRequestOptions() requestOptions.isSynchronous = false requestOptions.deliveryMode = .highQualityFormat let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] // TODO: Set change with .image to with fetchOptions: Gets all types let results: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions) // TODO: Change photoArray so that it imcludes all three types self.photoArray = photoArray.shiftRight() let asset = results.object(at: currentIndex) let size = CGSize(width: 700, height: 700) //You can change size here // TODO: Make this so that it applies to Images, Lives, and Videos manager.requestImage(for: asset, targetSize: size, contentMode: .aspectFill, options: requestOptions) { (image, _) in DispatchQueue.main.async { if let image = image { self.photoArray.removeLast() self.photoArray.append(BetterUIImage(image: image, asset: asset)) } else { print("error asset to image") } } } } func next() { if currentIndex == totalInitialPhotos { self.photoArray = [] return } else if currentIndex == totalInitialPhotos - 1 { self.photoArray.removeLast() } else if currentIndex == totalInitialPhotos - 2 { self.photoArray.removeLast() } getNextPhoto() currentIndex += 1 print(currentIndex) } func deletePhoto() { PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.deleteAssets(NSArray(array: self.photosToDelete)) }) } }
请问是否可以修改现有代码以同时返回图片和视频?我曾尝试将PHAsset.fetchAssets(with: .image, options: fetchOptions)改为PHAsset.fetchAssets(with: fetchOptions),但似乎没有效果,不过也没有报错。
有没有其他实现方式?我见过其他类似的应用,但网上找到的资料大多使用PhotosPicker,这不符合我的应用需求。
解决方案
你遇到的核心问题是:视频资产无法通过requestImage方法稳定获取有效图像,即便修改fetchAssets获取所有类型,视频对应的回调里image会为nil,导致无法加入数组。以下是具体修改方案:
1. 定义通用媒体类型替代BetterUIImage
首先需要一个能同时承载图片和视频缩略图的类型,替换现有的BetterUIImage:
enum MediaType { case image case video } struct MediaItem: Identifiable { let id = UUID() let thumbnail: UIImage let asset: PHAsset let type: MediaType }
然后将PhotosModel里的数组类型替换:
@Published var mediaArray: [MediaItem] = []
2. 封装通用缩略图请求方法
统一处理图片和视频的缩略图获取,避免重复逻辑:
private func requestThumbnail(for asset: PHAsset, targetSize: CGSize, completion: @escaping (UIImage?) -> Void) { let manager = PHImageManager.default() let options = PHImageRequestOptions() options.isSynchronous = false options.deliveryMode = .highQualityFormat options.resizeMode = .exact switch asset.mediaType { case .image: manager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { image, _ in completion(image) } case .video: // 直接用requestImage获取视频封面图,满足大部分场景需求 manager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFill, options: options) { image, _ in completion(image) } default: completion(nil) } }
3. 修改初始化资产获取逻辑
更新getInitialPhotos,确保只获取图片和视频,并用新的通用方法请求缩略图:
fileprivate func getInitialPhotos() { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] // 明确过滤出图片和视频,避免获取音频等其他类型 fetchOptions.predicate = NSPredicate(format: "mediaType == %d || mediaType == %d", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue) let results: PHFetchResult = PHAsset.fetchAssets(with: fetchOptions) totalInitialPhotos = results.count if results.count == 0 { DispatchQueue.main.async { self.errorString = "No media to display" } return } let fetchCount = min(results.count, 3) for i in 0..<fetchCount { let asset = results.object(at: i) let size = CGSize(width: 700, height: 700) requestThumbnail(for: asset, targetSize: size) { [weak self] image in guard let self = self, let image = image else { print("Failed to get thumbnail for asset \(asset.localIdentifier)") return } DispatchQueue.main.async { let mediaType: MediaType = asset.mediaType == .image ? .image : .video let mediaItem = MediaItem(thumbnail: image, asset: asset, type: mediaType) self.mediaArray.append(mediaItem) // 异步请求完成后统一反转数组保证顺序 if self.mediaArray.count == fetchCount { self.mediaArray = self.mediaArray.reversed() } } } } currentIndex = fetchCount print("getInitialPhotos() completed") }
4. 更新分页加载逻辑
修改getNextPhoto,适配新的媒体类型和请求方法:
fileprivate func getNextPhoto() { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] fetchOptions.predicate = NSPredicate(format: "mediaType == %d || mediaType == %d", PHAssetMediaType.image.rawValue, PHAssetMediaType.video.rawValue) let results: PHFetchResult = PHAsset.fetchAssets(with: fetchOptions) guard currentIndex < results.count else { return } self.mediaArray = mediaArray.shiftRight() let asset = results.object(at: currentIndex) let size = CGSize(width: 700, height: 700) requestThumbnail(for: asset, targetSize: size) { [weak self] image in guard let self = self, let image = image else { print("Failed to get thumbnail for asset \(asset.localIdentifier)") return } DispatchQueue.main.async { self.mediaArray.removeLast() let mediaType: MediaType = asset.mediaType == .image ? .image : .video self.mediaArray.append(MediaItem(thumbnail: image, asset: asset, type: mediaType)) } } }
5. 其他细节调整
- 将所有原来使用
photoArray的地方替换为mediaArray(比如next()方法中的数组操作) - 删除逻辑无需修改,
PHAssetChangeRequest.deleteAssets支持删除任意类型的PHAsset
为什么之前的修改没效果?
你只修改了fetchAssets的调用,但没有处理视频资产的缩略图请求逻辑。部分视频资产用requestImage无法返回有效图像,导致回调中image为nil,无法加入原有的photoArray。上述方案通过明确判断媒体类型、统一处理缩略图请求,解决了这个核心问题。
内容的提问来源于stack exchange,提问作者garrett
相关产品推荐
相关产品推荐

