SwiftUI with UIViewControllerRepresentable

1.2k Views Asked by At

I am trying to use a UIViewController representable in a swiftUi project. Specifically I am trying to press one button (assetOne) that allows the EU to select a video and then press another button (assetTwo) and it allows the user to select another video. Then the user will have the option to merge the videos (with a third button). I assumed that I would need to use a Coordinator to accomplish this but after seeing a SO solution without it I tried to do it without one. But when I run my project the build is successful but when I click on any of the buttons from the content view I get the error message below. What am I doing wrong? Do I need a Coordinator and how do I incorporate it with my current configuration?

Warning: Attempt to present <UIImagePickerController: 0x7fa05f827600> on <TempTest.MergeVideoViewController: 0x7fa05ed088c0> whose view is not in the window hierarchy!

Content View:

import SwiftUI

struct ContentView: View {
    
    let someView = ImagePicker()
    
    var body: some View {
        VStack {
            Button(action: {
                print("SwiftUI: assetOne button tapped")
                // Call func in SomeView()
                self.someView.assetOne()
            }) {
                Text("Asset One").foregroundColor(Color.black)
            }
            .background(Color.blue)
            .padding(10)
            .clipShape(Capsule())
}
//...

ImagePicker: UIViewControllerRepresentable

struct ImagePicker: UIViewControllerRepresentable{
    
    let someView = MergeVideoViewController()

func makeUIViewController(context: Context) -> MergeVideoViewController {
      someView
    }

    func updateUIViewController(_ uiViewController: MergeVideoViewController, context: Context) {}
    
    func assetOne() {
        someView.loadAssetOne()
    }
    //...
}

My UIViewController class:

class MergeVideoViewController: UIViewController {
    var firstAsset: AVAsset?
    var secondAsset: AVAsset?
    var audioAsset: AVAsset?
    var loadingAssetOne = false
    var activityMonitor: UIActivityIndicatorView!
    
func exportDidFinish(_ session: AVAssetExportSession) {
    // Cleanup assets
    activityMonitor.stopAnimating()
    firstAsset = nil
    secondAsset = nil
    audioAsset = nil

//...

func loadAssetOne() {
        //  func loadAssetOne(_ sender: AnyObject) {
        if savedPhotosAvailable() {
            loadingAssetOne = true
            VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
        }
    }

//...
1

There are 1 best solutions below

0
On

The ImagePicker is-a View, it should be somewhere in body.

Here is possible approach - the idea is to get controller reference back in SwiftUI and call its actions directly when needed.

struct ImagePicker: UIViewControllerRepresentable{
    let configure: (MergeVideoViewController) -> ()

    func makeUIViewController(context: Context) -> MergeVideoViewController {
       let someView = MergeVideoViewController()
       configure(someView)
       return someView
    }

    func updateUIViewController(_ uiViewController: MergeVideoViewController, context: Context) {}
}

struct ContentView: View {

    @State private var controller: MergeVideoViewController?

    var body: some View {
        VStack {
            ImagePicker {
                self.controller = $0
            }

            Button(action: {
                print("SwiftUI: assetOne button tapped")
                self.controller?.loadAssetOne()
            }) {
                Text("Asset One").foregroundColor(Color.black)
            }
            .background(Color.blue)
            .padding(10)
            .clipShape(Capsule())
        }
    }
}