SwiftUI getting data out of coordinator into content view

Viewed 1426

I'm building a QR code reader into my app, so far I have it open as a sheet and close when a qr/barcode is detected. The reader part of the app uses UIKit, I have the file QRCodeScan.swift which is the UIViewControllerRepresentable, the QR scanner returns the value of the code that it has found into the coordinator in this file. I can't seem to find any way to get the found code out of the coordinator into the original view.

This is the file QRCodeScan.

struct QRCodeScan: UIViewControllerRepresentable {
    
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    
    func makeUIViewController(context: Context) -> ScannerViewController {
        let vc = ScannerViewController()
        vc.delegate = context.coordinator
        return vc
    }
    
    func updateUIViewController(_ vc: ScannerViewController, context: Context) {
    }

    class Coordinator: NSObject, QRCodeScannerDelegate {
        @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
        
        func codeDidFind(_ foundCode: String) {
            print(foundCode)
            /*this is where the code comes to, need to return it from here */
            presentationMode.wrappedValue.dismiss()
        }
        
        var parent: QRCodeScan
        
        init(_ parent: QRCodeScan) {
            self.parent = parent
        }

    }
}

This is a cropped down version of the ContentView that calls the qr reader , this is where I need to get the found code back into

struct ContentView: View {
    
    @State var presentQRScanner = false
    
    var body: some View {
        NavigationView{
            Form{
                Section(header: Text("Info")){
                    Button("Scan Barcode"){
                        self.presentQRScanner = true
                    }
                        .sheet(isPresented: $presentQRScanner){QRCodeScan()}
                }
                
            }
            
            .navigationBarTitle(Text("New"), displayMode: .large)
            .navigationBarItems(trailing: Button("Save"){
                print("Button Pressed")
            })
        }
    }
}

I've hit a total roadblock here, I can't find any resources that allow me to pass the data back from the coordinator, maybe I'm implementing something wrong but I can't seem to adapt any other solutions to fit

Any help is much appreciated.

Thanks

2 Answers

You may have already solved this, but the solution is to use an @State variable in your ContentView linked to an @Binding variable in your QRCodeScan Struct and the Coordinator Class.

Check out this answer: Accessing MKMapView elements as UIViewRepresentable in the main (ContentView) SwiftUI view

Something like this should do the trick, but I suggest reading over the more detailed answer I linked:

 struct QRCodeScan: UIViewControllerRepresentable {

    @Binding var code: String

func makeCoordinator() -> Coordinator {
    return Coordinator(code: $code)
}

func makeUIViewController(context: Context) -> ScannerViewController {
    let vc = ScannerViewController()
    vc.delegate = context.coordinator
    return vc
}

func updateUIViewController(_ vc: ScannerViewController, context: Context) {
}

class Coordinator: NSObject, QRCodeScannerDelegate {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

   @Binding var code: String

   init(code: Binding<String>) {
   _code = code
   }

    func codeDidFind(_ foundCode: String) {
        print(foundCode)
        /*this is where the code comes to, need to return it from here */
        self.code = foundCode
        presentationMode.wrappedValue.dismiss()
    }

    var parent: QRCodeScan

    init(_ parent: QRCodeScan) {
        self.parent = parent
    }
}
}

In your ContentView, create @State var code, then you can call QRCodeScan(code: $code).

I already answered this for you in this answer: SwiftUI go back programmatically from representable to View.

Alternatively, you can drive this from the presenter too by creating a closure on the QRCodeScan that gets invoked with the code and you have your presenter dismiss.

EDITS: I have inserted my suggestions into your code to show you who owns what and how to pass information around.

struct QRCodeScan: UIViewControllerRepresentable {

    /// a closure called with the found code
    var onCodeScanned: (String) -> Void

    class Coordinator: NSObject, QRCodeScannerDelegate {

        func codeDidFind(_ foundCode: String) {
            // call the parent's `onCodeScanned` closure here
            parent.onCodeScanned(foundCode)

            presentationMode.wrappedValue.dismiss()
        }

        var parent: QRCodeScan

        // init, makeViewController, etc.
    }
}

and in the presenter:

struct ContentView: View {

    @State var presentQRScanner = false

    var body: some View {
        NavigationView{
            Form{
                Section(header: Text("Info")){
                    Button("Scan Barcode"){
                        self.presentQRScanner = true
                    }
                    .sheet(isPresented: $presentQRScanner){
                        // when you create the VC representable, pass in your closure to handle found codes
                        QRCodeScan(onCodeScanned: {
                            self.processFoundCode($0)
                        })
                    }
                }

            }
            // .theRest()
        }
    }

    func processFoundCode(_ code: String) {
        // process it

        // dimiss sheet
        presentQRScanned = false
    }
}

In other words, your closure will pass that data up the chain to your ContentView.

Related