在 TabView 中使用 VNDocumentCameraViewController 作为 UIViewControllerRepresentable

Use VNDocumentCameraViewController as a UIViewControllerRepresentable in a TabView

我在名为 ScanView 的视图中将 VNDocumentCameraViewController 作为 UIViewControllerRepresentable 嵌入到作为第二个屏幕的 TabView 中。关闭 VNDocumentCameraViewController(取消或保存扫描)时,我希望选项卡视图返回到我的第一个屏幕。那部分就像一个魅力。

我的问题是,当回到我的 VNDocumentCameraViewController 时,我想重新实例化该控制器并重新开始——这是我无法弄清楚如何实现的。

我知道我的 ContentView 保留对 ScanView 的引用是我的 UIViewControllerRepresentable 没有重新实例化的原因——我如何手动执行此操作?

代码如下:

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 })
    }
}

这就是 TabView 的工作原理:它保存每个选项卡的状态

你可以破解它,但请不要:当 iOS 用户看到选项卡视图时,他希望如果他从一个选项卡切换到另一个选项卡再返回,他不会丢失任何状态

只需制作一个按钮,将您的文档选择器显示为 .sheet(...) 或使用 NavigationLink 推入导航控制器,您的问题就会消失。

如果您使用其中一种方法,则不需要重置控制器的状态,因为它会在您每次呈现视图时重新创建

如果你仍然想这样做,你可以用 UINavigationController 包装你的控制器并在 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。如果 TabView 它仍然无法工作,因为状态已保存,要破解你可以将 .id(tabSelection) 添加到你的 ScanView

感谢 Philip 上面的回答,建议我可以使用 .sheet,我想出了这个新的 ScanView,它使用 .fullScreenCover.

完成这项工作,但它会覆盖标签栏(我认为我可以接受)。

struct ScanView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @State private var showFullscreen = true
    @Binding var tabSelection: Int
    
    var body: some View {
        EmptyView()
            .fullScreenCover(isPresented: $showFullscreen, onDismiss: {
                presentationMode.wrappedValue.dismiss()
            }) {
                DocumentScanningViewAdapter(onDismiss: {
                    showFullscreen = false
                    tabSelection = 1
                })
            }
            .onAppear {
                showFullscreen = true
            }
    }
}