robbo5899
robbo5899

Reputation: 99

SwiftUI go back programmatically from representable to View

I'm trying to setup a qr reader within a new swift ui app.

I can get load the UIKit qr reader view with this line

NavigationLink(destination: QRCodeScan()){Text("Scan QR")}

This is my ViewControllerRepresentable

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 {
    func codeDidFind(_ code: String) {
        print(code)
        //Go back to the last page, take 'code' with you
    }

    var parent: QRCodeScan

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

}

At the line 'Go back to the last page...' I need to programmatically return to the page which sent the user to the qr scanner. The page loads with a navigation back button, I pretty much need to replicate this buttons behaviour to call when I need

Any help/pointers appreciated

tia

Upvotes: 2

Views: 3196

Answers (4)

Ihor Chernysh
Ihor Chernysh

Reputation: 496

To go back just add the presentaionMode property into the QRCodeScan:

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

And when your delegate executes - need to call the following:

parent.presentationMode.wrappedValue.dismiss()

So the full code will look the following way:

struct QRCodeScan: UIViewControllerRepresentable {
  
  @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

  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 {
    func codeDidFind(_ code: String) {
        //Go back to the last page, take 'code' with you
        parent.presentationMode.wrappedValue.dismiss()
    }

    var parent: QRCodeScan

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

Upvotes: 0

Yaroslav Y.
Yaroslav Y.

Reputation: 357

You can do it with the following overload of the NavigationLink. It's available since iOS 13 and later.

Here's the code.

Pay attention to passing $isShowingView binding to the both NavigationLink object and to the ChildView that you want to go out on button tap.

struct ContentView: View {
    
    @State var isShowingChildView = false
    
    var body: some View {
        NavigationView {
            NavigationLink(isActive: $isShowingChildView) {
                ChildView(isShowingView: $isShowingChildView)
            } label: {
                Text("Open new view")
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
struct ChildView: View {
        
    @Binding
    var isShowingView: Bool
    
    var body: some View {
        VStack {
            Button("Back to parent view") {
                isShowingView = false
            }
        }
    }
}

Upvotes: 0

Sorin Lica
Sorin Lica

Reputation: 7636

struct ContentView: View {
    @State var isActive = false
    @State var code = ""
    var body: some View {
        NavigationView {
            ZStack {
                NavigationLink(destination: DetailView(isActive: $isActive, code: $code), isActive: $isActive, label: { EmptyView() })
                Button(action: {
                    self.isActive.toggle()
                }, label: {
                    Text("navigate")
                })
            }
        }
    }
}
struct DetailView: View {

    @Binding var isActive: Bool
    @Binding var code: String

    var body: some View {
        Button(action: {
            self.code = "new code"
            self.isActive.toggle()
        }) {
            Text("Back")
        }
    }
}

This might help you, use isActive parameter of NavigationLink to navigate back and forth

Upvotes: 3

Procrastin8
Procrastin8

Reputation: 4503

The short answer is you can't do that right now. There is neither a binding nor an environment value to set that can trigger this. My guess is there will be some kind of environment value akin to presentationMode that you can tap into but it isn't currently advertised.

You could try the current presentationMode but my real suggestion is to present your QR scanner as a sheet rather than a push. This may actually make more sense from a navigational standpoint anyway. To do it this way, in your presenter set up a @State var to handle when it's presented.

@State var presentQRScanner = false

var body: some View {
    Button("Scan") {
        self.presentQRScanner = true
    }
    .sheet(isPresented: $presentQRScanner) { QRCodeScan() }
}

Then, when you want to programmatically dismiss, your UIViewControllerRepresentable:

@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

func scannedCode() {
    presentationMode.wrappedValue.dismiss()
}

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.

var onCodeScanned: (Code) -> Void = { _ in }

func scannedCode() {
    onCodeScanned(code)
}

and in the presenter:

var body: some View {
    Button("Scan") {
        self.presentQRScanner = true
    }
    .sheet(isPresented: $presentQRScanner) { 
        QRCodeScan(onCodeScanned: { 
            self.process($0)
            self.presentQRScanner = false
        })
    }
}

EDIT: was not aware of the isActive binding, that should actually work for you if you still want to push your view on the nav stack instead of present it.

Upvotes: 1

Related Questions