swiftswiftuiuikituiimagepickercontrolleravplayer

Pick and play a video in SwiftUI — how convert from UIKit code?


I'm working on a camera app and I've got a problem. I've never used UIKit to build an app, but a lot of the reference code does. So I tried to convert it using swiftUI but I failed. There is UIKit code which I want to convert to SwiftUI.

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 {
}

Here's what I've tried, and the compilation passes, but it only does UIImagePickerController() , and the delegate function I wrote doesn't work.

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()}
                
                // 2
                parent.presentationMode.wrappedValue.dismiss()
                    //3
                let player = AVPlayer(url: url)
                let vcPlayer = AVPlayerViewController()
                vcPlayer.player = player
                return vcPlayer
            }
        
    }
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
}

Solution

  • The problem you have is that you haven't implemented the correct UIImagePickerControllerDelegate function signature.

    Your Coordinator has:

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) -> UIViewController
    

    while the correct method is:

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
    

    The method won't get called unless the signature matches exactly.


    A solution

    The UIImagePickerController is just used to select the image or video, you'll need additional cod to play the selected video. Luckily SwiftUI has a VideoPlayer that makes it easy:

    import UniformTypeIdentifiers
    
    struct ContentView: View {
        
        @State private var isShowVideoLibrary = false
        @State private var url: URL?
        
        var body: some View {
            Group {
                if let url {
                    VideoPlayer(player: AVPlayer(url: url))
                } else {
                    VStack {
                        HStack{
                            Button {
                                isShowVideoLibrary.toggle()
                            } label: {
                                Text("Play video")
                            }
                        }
                    }
                }
            }
            .sheet(isPresented: $isShowVideoLibrary) {
                VideoPicker(sourceType: .photoLibrary) { url in
                    self.url = url
                    isShowVideoLibrary = false
                }
            }
            
        }
    }
    
    struct VideoPicker: UIViewControllerRepresentable {
        
        var sourceType: UIImagePickerController.SourceType = .photoLibrary
        let didFinish: (URL?) -> Void
        
        
        func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIViewController {
            let mediaUI = UIImagePickerController()
            mediaUI.sourceType = sourceType
            mediaUI.mediaTypes = [UTType.movie.identifier]
            mediaUI.allowsEditing = true
            mediaUI.delegate = context.coordinator
            return mediaUI
        }
    
        func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {        
        }
        
        final class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
            
            let didFinish: (URL?) -> Void
            
            init(didFinish: @escaping (URL?) -> Void) {
                self.didFinish = didFinish
            }
            
            // This func passes the URL back to the calling View
            func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
                guard
                    let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
                    mediaType == UTType.movie.identifier,
                    let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
                else {
                    didFinish(nil)
                    return
                    
                }
                
                didFinish(url)
            }
        }
        
        func makeCoordinator() -> Coordinator {
            Coordinator(didFinish: didFinish)
        }
    }