I have VNDocumentCameraViewController
as a UIViewControllerRepresentable
in a view called ScanView
embedded in a TabView
as the 2nd screen. On dismissing the VNDocumentCameraViewController
(either on cancelling or on saving of the scan), I want the tab view to get back to my first screen. That part works like a charm.
My issue is though that when coming back to my VNDocumentCameraViewController
, I want to reinstantiate that controller and start over–which is what I cannot figure out on how to achieve that.
I am aware that my ContentView
keeping a reference to the ScanView
is why my UIViewControllerRepresentable
is not reinstantiated–how can I do that manually?
Here's the code:
import SwiftUI
@main
struct so_VisionKitInTabsApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State private var tabSelection = 1
var body: some View {
TabView(selection: $tabSelection) {
Text("First View")
.tabItem { Text("First View") }
.tag(1)
ScanView(tabSelection: $tabSelection)
.tabItem { Text("Scan View") }
.tag(2)
}
}
}
import VisionKit
struct DocumentScanningViewAdapter: UIViewControllerRepresentable {
typealias UIViewControllerType = VNDocumentCameraViewController
let onDismiss: () -> ()
func makeCoordinator() -> Coordinator {
Coordinator(parent: self)
}
func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
let vc = VNDocumentCameraViewController()
vc.delegate = context.coordinator
return vc
}
func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) { }
class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate {
var parent: DocumentScanningViewAdapter
init(parent: DocumentScanningViewAdapter) {
self.parent = parent
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
print("Finished successfully…")
parent.onDismiss()
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
print("Cancelled…")
resetCoordinator(for: controller)
parent.onDismiss()
}
func resetCoordinator(for controller: VNDocumentCameraViewController) {
controller.delegate = parent.makeCoordinator()
}
}
}
struct ScanView: View {
@Binding var tabSelection: Int
var body: some View {
DocumentScanningViewAdapter(onDismiss: { tabSelection = 1 })
}
}
This is how TabView
works: it saves state of each tab
You could've hack it, but please don't: when iOS user sees tab view he expects that if he switch from one tab to an other and back he won't loose any state
Instead just make a button which present your document picker as a .sheet(...)
or push in the navigation controller using NavigationLink
, and your problem will be gone.
If you use one of this approaches, you don't need to reset state of your controller because it's gonna be recreated each time you present a view
In case you still wanna do that, you can wrap your controller with a UINavigationController
and initialize your own controller it in updateUIViewController
func makeUIViewController(context: Context) -> UINavigationController {
let controller = UINavigationController()
controller.isNavigationBarHidden = true
return controller
}
func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {
let vc = VNDocumentCameraViewController()
vc.delegate = context.coordinator
uiViewController.viewControllers = [vc]
}
updateUIViewController
gets called each time view re-render is needed. In case with TabView
it's still not gonna work, because state is saved, to hack that you can add .id(tabSelection)
to your ScanView