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

SwiftUI实现视频选择与播放:UIKit代码转换遇阻求助

问题:UIKit视频选择播放代码转SwiftUI后代理函数失效

我正在开发一款相机应用,此前从未使用UIKit开发应用,但参考代码多为UIKit实现。我需要将一段实现视频选择与播放的UIKit代码转换为SwiftUI,自行编写的代码可编译通过,但代理函数无法正常工作。


原UIKit代码

VideoHelper工具类

static func startMediaBrowser(
    delegate: UIViewController & UINavigationControllerDelegate & UIImagePickerControllerDelegate,
    sourceType: UIImagePickerController.SourceType
  ) {
    guard UIImagePickerController.isSourceTypeAvailable(sourceType)
      else { return }

    let mediaUI = UIImagePickerController()
    mediaUI.sourceType = sourceType
    mediaUI.mediaTypes = [kUTTypeMovie as String]
    mediaUI.allowsEditing = true
    mediaUI.delegate = delegate
    delegate.present(mediaUI, animated: true, completion: nil)
  }

视频播放视图控制器

import AVKit
import MobileCoreServices
import UIKit

class PlayVideoViewController: UIViewController {
  @IBAction func playVideo(_ sender: AnyObject) {
    VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
  }
}

// MARK: - UIImagePickerControllerDelegate
extension PlayVideoViewController: UIImagePickerControllerDelegate {
  func imagePickerController(
    _ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
  ) {
    guard
      let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
      mediaType == (kUTTypeMovie as String),
      let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
      else { return }

    dismiss(animated: true) {
      let player = AVPlayer(url: url)
      let vcPlayer = AVPlayerViewController()
      vcPlayer.player = player
      self.present(vcPlayer, animated: true, completion: nil)
    }
  }
}

// MARK: - UINavigationControllerDelegate
extension PlayVideoViewController: UINavigationControllerDelegate {
}

我尝试的SwiftUI代码

主视图

import SwiftUI
import AVKit
import MobileCoreServices
import UIKit

struct ContentView: View {
    @State private var isShowVideoLibrary = false
    @State private var image = UIImage()
    @State private var isShowCamara = false
    
    var body: some View {
        VStack {
            HStack{
                Button {
                    isShowVideoLibrary.toggle()
                } label: {
                    Text("Play video")
                }
            }
        }
        .sheet(isPresented: $isShowVideoLibrary) {
            VideoPicker(sourceType: .photoLibrary)
        }
    }
}

视频选择器

struct VideoPicker: UIViewControllerRepresentable {
    var sourceType: UIImagePickerController.SourceType = .photoLibrary
    @Environment(\.presentationMode) private var presentationMode
    
    func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIViewController {
        let mediaUI = UIImagePickerController()
        mediaUI.sourceType = sourceType
        mediaUI.mediaTypes = [kUTTypeMovie as String]
        mediaUI.allowsEditing = true
        mediaUI.delegate = context.coordinator
        return mediaUI
    }
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
        
    }
    final class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
        var parent: VideoPicker
        init(_ parent: VideoPicker) {
            self.parent = parent
        }
        private func imagePickerController(
            _ picker: UIImagePickerController,
            didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) -> UIViewController {
                guard
                    let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
                    mediaType == (kUTTypeMovie as String),
                    let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
                else { return AVPlayerViewController()}
                
                parent.presentationMode.wrappedValue.dismiss()
                
                let player = AVPlayer(url: url)
                let vcPlayer = AVPlayerViewController()
                vcPlayer.player = player
                return vcPlayer
            }
        
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
}

问题原因与修正方案

核心问题

  1. 代理方法定义错误:系统的UIImagePickerControllerDelegate代理方法imagePickerController(_:didFinishPickingMediaWithInfo:)是无返回值的,你错误地给它添加了返回值UIViewController,还标记为private,导致系统无法识别并调用该方法。
  2. SwiftUI环境下的弹窗逻辑错误:在SwiftUI中不能直接在协调器里返回视图控制器,需要通过绑定值将视频URL传递到主视图,再用SwiftUI的弹窗组件展示播放器。

修正后的完整代码

主视图(ContentView)

import SwiftUI
import AVKit

struct ContentView: View {
    @State private var isShowVideoLibrary = false
    // 存储选中的视频URL,用于触发播放器弹窗
    @State private var selectedVideoURL: URL?
    
    var body: some View {
        VStack {
            Button {
                isShowVideoLibrary.toggle()
            } label: {
                Text("Play video")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
        }
        // 视频选择器弹窗
        .sheet(isPresented: $isShowVideoLibrary) {
            VideoPicker(sourceType: .photoLibrary) { url in
                selectedVideoURL = url
            }
        }
        // 视频播放器全屏弹窗
        .fullScreenCover(item: $selectedVideoURL) { url in
            AVPlayerControllerRepresentable(player: AVPlayer(url: url))
        }
    }
}

// 封装AVPlayerViewController为SwiftUI视图
struct AVPlayerControllerRepresentable: UIViewControllerRepresentable {
    let player: AVPlayer
    
    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let playerVC = AVPlayerViewController()
        playerVC.player = player
        // 自动播放
        player.play()
        return playerVC
    }
    
    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {}
}

视频选择器(VideoPicker)

import UIKit
import MobileCoreServices

struct VideoPicker: UIViewControllerRepresentable {
    let sourceType: UIImagePickerController.SourceType
    // 回调闭包,用于传递选中的视频URL
    let onVideoSelected: (URL) -> Void
    
    func makeUIViewController(context: Context) -> UIImagePickerController {
        let mediaUI = UIImagePickerController()
        mediaUI.sourceType = sourceType
        mediaUI.mediaTypes = [kUTTypeMovie as String]
        mediaUI.allowsEditing = true
        mediaUI.delegate = context.coordinator
        return mediaUI
    }
    
    func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    final class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
        let parent: VideoPicker
        
        init(_ parent: VideoPicker) {
            self.parent = parent
        }
        
        // 修正:移除private修饰符,改为无返回值的正确代理方法
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            guard
                let mediaType = info[.mediaType] as? String,
                mediaType == kUTTypeMovie as String,
                let url = info[.mediaURL] as? URL
            else {
                picker.dismiss(animated: true)
                return
            }
            
            // 关闭选择器,然后通过回调传递URL
            picker.dismiss(animated: true) {
                self.parent.onVideoSelected(url)
            }
        }
        
        // 处理取消选择的情况
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            picker.dismiss(animated: true)
        }
    }
}

内容的提问来源于stack exchange,提问作者Yung Sheng Zeng

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 23:35:29