import Photos import SwiftUI enum GalleryMediaType { case video case photo } struct GalleryItem: Identifiable { let id: String let type: GalleryMediaType var thumbnail: Image? var duration: String? } extension GalleryItem { static func fetchAll() async -> [GalleryItem] { await Task { let fetchOptions = PHFetchOptions() fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)] let assets = PHAsset.fetchAssets(with: fetchOptions) var tmpGalleryItems: [GalleryItem] = [] assets.enumerateObjects { asset, _, _ in if asset.mediaType == .image { let item = GalleryItem(id: asset.localIdentifier, type: .photo, thumbnail: nil, duration: nil) tmpGalleryItems.append(item) } if asset.mediaType == .video { let item = GalleryItem(id: asset.localIdentifier, type: .video, thumbnail: nil, duration: asset.duration.minAndSec) tmpGalleryItems.append(item) } } return tmpGalleryItems }.value } mutating func fetchThumbnail() async throws { guard let asset = PHAsset.fetchAssets(withLocalIdentifiers: [id], options: nil).firstObject else { return } let size = CGSize(width: Const.galleryGridSize, height: Const.galleryGridSize) switch type { case .photo: let originalImage = try await PHImageManager.default().getPhoto(for: asset) let cropped = try await originalImage.scaleAndCropImage(size) thumbnail = Image(uiImage: cropped) case .video: let avAsset = try await PHImageManager.default().getVideo(for: asset) let cropped = try await avAsset.generateVideoThumbnail(size) thumbnail = Image(uiImage: cropped) } } }