You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 18:59:51