SwiftUI中如何将PhotosPickerItem转为可播放视频并实现列表展示与播放
解决方案:从PhotosPickerItem获取视频资源并实现列表/网格展示与播放
1. 定义视频数据模型
先创建一个Identifiable模型存储视频关键信息,方便后续列表/网格展示:
import SwiftUI import AVKit import PhotosUI import UIKit struct VideoAsset: Identifiable { let id: UUID let url: URL let thumbnail: UIImage? }
2. 扩展ContentView:处理视频资源加载与展示
修改原有代码,添加视频资源存储数组、加载方法,并实现列表/网格两种展示方式:
struct ContentView : View { @State private var selectedAssets: [PhotosPickerItem] = [] @State private var authorizationStatus: PHAuthorizationStatus = .notDetermined @State private var errorMessage: String? @State private var videoAssets: [VideoAsset] = [] // 存储可播放的视频资源 var body: some View { NavigationStack { // 包裹导航栈支持页面跳转 VStack{ if authorizationStatus == .authorized || authorizationStatus == .limited{ PhotosPicker(selection: $selectedAssets, matching: .videos) { Text("Select Multiple Videos \(selectedAssets.count)") } .onChange(of: selectedAssets) { _ in loadVideoAssets() // 选中项变化时自动加载视频 } if !videoAssets.isEmpty { // --- 列表展示(启用)--- List(videoAssets) { asset in videoListItem(asset: asset) } // --- 网格展示(注释列表后启用)--- // LazyVGrid(columns: [GridItem(.adaptive(minimum: 120))], spacing: 16) { // ForEach(videoAssets) { asset in // videoGridItem(asset: asset) // } // } // .padding() } else if !selectedAssets.isEmpty { Text("正在加载视频...") } else { Text("No videos selected") } } else if authorizationStatus == .denied { HStack{ Image(systemName: "info.circle") Text("Access Denied") } } else if authorizationStatus == .notDetermined { Button("Request Access") { PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in self.authorizationStatus = status } } } else { Text("An error occurred while accessing the Photos library.") } if let errorMessage = errorMessage { Text(errorMessage) .foregroundColor(.red) } } } .onAppear { PHPhotoLibrary.requestAuthorization(for: .readWrite) { status in self.authorizationStatus = status } } } // 列表项视图 private func videoListItem(asset: VideoAsset) -> some View { NavigationLink(destination: VideoPlayerView(videoURL: asset.url)) { HStack { if let thumbnail = asset.thumbnail { Image(uiImage: thumbnail) .resizable() .frame(width: 80, height: 80) .cornerRadius(8) } else { Image(systemName: "film") .frame(width: 80, height: 80) .foregroundColor(.gray) } Text(asset.url.lastPathComponent) .font(.subheadline) } } } // 网格项视图 private func videoGridItem(asset: VideoAsset) -> some View { NavigationLink(destination: VideoPlayerView(videoURL: asset.url)) { VStack { if let thumbnail = asset.thumbnail { Image(uiImage: thumbnail) .resizable() .aspectRatio(contentMode: .fill) .frame(height: 120) .clipped() .cornerRadius(8) } else { Image(systemName: "film") .frame(height: 120) .foregroundColor(.gray) } Text(asset.url.lastPathComponent) .font(.caption) .lineLimit(1) } } } // 加载选中的视频资源 private func loadVideoAssets() { videoAssets.removeAll() errorMessage = nil for item in selectedAssets { Task { do { // 获取视频本地URL guard let url = try await item.loadTransferable(type: URL.self) else { throw NSError(domain: "VideoLoadError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无法获取视频URL"]) } // 获取视频缩略图 let thumbnail = try await item.loadTransferable(type: Image.self)?.uiImage let videoAsset = VideoAsset(id: UUID(), url: url, thumbnail: thumbnail) DispatchQueue.main.async { videoAssets.append(videoAsset) } } catch { DispatchQueue.main.async { errorMessage = "加载视频失败: \(error.localizedDescription)" } } } } } } // 扩展Image:转换为UIImage extension Image { var uiImage: UIImage? { let renderer = ImageRenderer(image: self) return renderer.uiImage } }
3. 实现视频播放页面
创建专门的播放视图,使用VideoPlayer组件实现基础播放控制:
struct VideoPlayerView: View { let videoURL: URL private let player: AVPlayer init(videoURL: URL) { self.videoURL = videoURL self.player = AVPlayer(url: videoURL) } var body: some View { VideoPlayer(player: player) .onAppear { player.play() // 页面出现时自动播放 } .onDisappear { player.pause() // 页面消失时暂停 player.seek(to: .zero) // 重置播放位置 } .navigationTitle(videoURL.lastPathComponent) .navigationBarTitleDisplayMode(.inline) } }
关键说明
- 资源加载:通过
PhotosPickerItem的loadTransferable异步方法获取视频本地URL和缩略图,需在Task中处理异步逻辑。 - 展示切换:注释列表代码即可启用网格布局,
LazyVGrid的adaptive布局会自动适配屏幕宽度。 - 播放逻辑:在播放页面通过
onAppear和onDisappear事件实现自动播放、暂停及位置重置。
内容的提问来源于stack exchange,提问作者Vicky0903
相关产品推荐
相关产品推荐

