Calling functions from UIViewController in SwiftUI

There are probably multiple solutions to this problem, but one way or another, you’ll need to find a way to keep a reference to or communicate with the UIViewController. Because SwiftUI views themselves are pretty transient, you can’t just store a reference in the view itself, because it could get recreated at any time.

Tools to use:

  • ObservableObject — this will let you store data in a class instead of a struct and will make it easier to store references, connect data, etc

  • Coordinator — in a UIViewRepresentable, you can use a Coordinator pattern which will allow you to store references to the UIViewController and communicate with it

  • Combine Publishers — these are totally optional, but I’ve chosen to use them here since they’re an easy way to move data around without too much boilerplate code.

import SwiftUI
import Combine

struct ContentView: View {
    @ObservedObject var vcLink = VCLink()
    var body: some View {
        VStack {
            VCRepresented(vcLink: vcLink)
            Button("Take photo") {
                vcLink.takePhoto()
            }
        }
    }
}

enum LinkAction {
    case takePhoto
}

class VCLink : ObservableObject {
    @Published var action : LinkAction?
    
    func takePhoto() {
        action = .takePhoto
    }
}

class CustomVC : UIViewController {
    func action(_ action : LinkAction) {
        print("\(action)")
    }
}

struct VCRepresented : UIViewControllerRepresentable {
    var vcLink : VCLink
    
    class Coordinator {
        var vcLink : VCLink? {
            didSet {
                cancelable = vcLink?.$action.sink(receiveValue: { (action) in
                    guard let action = action else {
                        return
                    }
                    self.viewController?.action(action)
                })
            }
        }
        var viewController : CustomVC?
        
        private var cancelable : AnyCancellable?
    }
    
    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
    
    func makeUIViewController(context: Context) -> CustomVC {
        return CustomVC()
    }
    
    func updateUIViewController(_ uiViewController: CustomVC, context: Context) {
        context.coordinator.viewController = uiViewController
        context.coordinator.vcLink = vcLink
    }
}

What happens here:

  1. VCLink is an ObservableObject that I’m using as a go-between to communicate between views
  2. The ContentView has a reference to the VCLink — when the button is pressed, the Publisher on VCLink communicates that to any subscribers
  3. When the VCRepresented is created/updated, I store a reference to the ViewController and the VCLink in its Coordinator
  4. The Coordinator takes the Publisher and in its sink method, performs an action on the stored ViewController. In this demo, I’m just printing the action. In your example, you’d want to trigger the photo itself.

Leave a Comment