How to implement a UIViewController delegate when working with SwiftUI
Learn how to use SwiftUI's coordinators to communicate with UIKit.
08 May 2023 · 4 min read
When building iOS applications, we sometimes need to use UIKit's UIViewController in a SwiftUI view, for example due to an older code base. When doing so, we may need to pass data from the view controller to the SwiftUI view. For that, SwiftUI provides so called coordinators.
Let's look at an example to understand how they work.

Let's say, we'd like to use the native DataScannerViewController to implement a data scanner in a SwiftUI application.
To be more precise, what we'd like to achieve is to open the DataScannerViewController from a SwiftUI view and after the user has finished scanning a QR code, to get that scanned data communicated back to our SwiftUI view.
As a first step, let's look at how to show the scanner view controller from a SwiftUI view. For that, SwiftUI provides the UIViewControllerRepresentable protocol:
struct ScannerView: UIViewControllerRepresentable {@Binding var code: String?func makeUIViewController(context: Context) -> DataScannerViewController {let viewController = DataScannerViewController(recognizedDataTypes: [.barcode(symbologies: [.qr])])return viewController}func updateUIViewController(_ uiViewController: DataScannerViewController, context: Context) {}}
With the code above, we are now able to use the ScannerView in SwiftUI, for example:
struct ContentView: View {@State private var code: String?@State private var showingScanner = falsevar body: some View {VStack {Text(code ?? "")Button("Scan QR code") {showingScanner = true}}.sheet(isPresented: $showingScanner) {ScannerView(code: $code)}}}
So far, the code binding we pass into the scanner view never gets updated by it. To be able to do that, we need to setup the scanner view controller's delegate at first. Only reference types can conform to DataScannerViewControllerDelegate, so we can't use the ScannerView itself since it's a struct which is not a reference type.
For those cases, SwiftUI provides a built in solution. Any UIViewControllerRepresentable type can declare a nested Coordinator reference type and implement the makeCoordinator() method:
struct ScannerView: UIViewControllerRepresentable {class Coordinator {@Binding var code: String?init(code: Binding<String?>) {_code = code}}func makeCoordinator() -> Coordinator {Coordinator(code: $code)}...}
We can then extend the coordinator to serve as DataScannerViewControllerDelegate:
class Coordinator: DataScannerViewControllerDelegate {@Binding var code: String?init(code: Binding<String?>) {_code = code}func dataScanner(_ dataScanner: DataScannerViewController, didAdd addedItems: [RecognizedItem], allItems: [RecognizedItem]) {guard let item = allItems.first else { return }switch item {case .barcode(let recognizedCode):print(recognizedCode.payloadStringValue)code = recognizedCode.payloadStringValuedefault:break}}}
SwiftUI will automatically call this method and associate the returned object with the context that is passed into the makeUIViewController method. There, we can use it to set the scanner view controller delegate:
func makeUIViewController(context: Context) -> DataScannerViewController {let viewController = DataScannerViewController(recognizedDataTypes: [.barcode(symbologies: [.qr])])viewController.delegate = context.coordinatorreturn viewController}

Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips