You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化VNDocumentCameraViewController扫描数据存入Swift Data模型?

优化扫描图片数据传递与SwiftData存储的方案

你的问题根源在于**@Binding传递大体积Data值类型**:每次传递都会触发值拷贝,同时Binding的更新会强制依赖它的视图(比如FolioView)重绘,双重开销导致卡顿。以下是几个更优的实现方案:

方案1:用闭包回调替代@Binding(最直接的优化)

放弃通过Binding传递Data,改用闭包在扫描完成后直接触发存储逻辑,避免值拷贝和不必要的视图刷新。

修改DocumentCameraView:

struct DocumentCameraView: UIViewControllerRepresentable {
    // 替换@Binding为闭包
    var onScanComplete: (Data) -> Void
    
    func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
        let vc = VNDocumentCameraViewController()
        vc.delegate = context.coordinator
        return vc
    }
    
    func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate {
        let parent: DocumentCameraView
        
        init(parent: DocumentCameraView) {
            self.parent = parent
        }
        
        func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
            guard scan.pageCount > 0 else {
                controller.dismiss(animated: true)
                return
            }
            // 获取扫描图片的Data
            let image = scan.imageOfPage(at: 0)
            guard let imageData = image.jpegData(compressionQuality: 0.8) else {
                controller.dismiss(animated: true)
                return
            }
            // 调用闭包传递数据
            parent.onScanComplete(imageData)
            controller.dismiss(animated: true)
        }
    }
}

在FolioView中调用:

struct FolioView: View {
    @Environment(\.modelContext) private var modelContext
    
    var body: some View {
        Button("扫描名片") {
            // 弹出DocumentCameraView
            let vc = UIHostingController(rootView: DocumentCameraView { imageData in
                // 直接在闭包里执行存储逻辑
                self.addBusinessCard(imageData: imageData)
            })
            UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: true)
        }
    }
    
    private func addBusinessCard(imageData: Data) {
        let newCard = BusinessCard(imageData: imageData)
        modelContext.insert(newCard)
        do {
            try modelContext.save()
        } catch {
            // 处理保存错误
            print("保存失败:\(error.localizedDescription)")
        }
    }
}

方案2:直接在DocumentCameraView中完成SwiftData存储(性能最优)

如果FolioView只是作为中转触发扫描,完全可以跳过数据传递环节,直接在扫描完成后将数据存入SwiftData,彻底消除大Data的传递开销。

修改DocumentCameraView:

struct DocumentCameraView: UIViewControllerRepresentable {
    // 注入SwiftData的上下文
    @Environment(\.modelContext) private var modelContext
    
    func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
        let vc = VNDocumentCameraViewController()
        vc.delegate = context.coordinator
        return vc
    }
    
    func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate {
        let parent: DocumentCameraView
        
        init(parent: DocumentCameraView) {
            self.parent = parent
        }
        
        func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
            guard scan.pageCount > 0 else {
                controller.dismiss(animated: true)
                return
            }
            let image = scan.imageOfPage(at: 0)
            guard let imageData = image.jpegData(compressionQuality: 0.8) else {
                controller.dismiss(animated: true)
                return
            }
            // 直接在Coordinator中创建并存储模型
            let newCard = BusinessCard(imageData: imageData)
            parent.modelContext.insert(newCard)
            do {
                try parent.modelContext.save()
            } catch {
                print("保存失败:\(error.localizedDescription)")
            }
            controller.dismiss(animated: true)
        }
    }
}

在FolioView中调用:

struct FolioView: View {
    var body: some View {
        Button("扫描名片") {
            let vc = UIHostingController(rootView: DocumentCameraView())
            UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: true)
        }
    }
}

方案3:用Combine实现异步数据流(适合复杂场景)

如果你的项目已经使用Combine,或者需要多个视图监听扫描结果,可以用PassthroughSubject来传递数据,避免值类型拷贝的同时实现响应式数据流。

定义一个扫描数据发布者:

import Combine

class ScanDataPublisher {
    static let shared = ScanDataPublisher()
    let scanDataSubject = PassthroughSubject<Data, Never>()
}

修改DocumentCameraView:

struct DocumentCameraView: UIViewControllerRepresentable {
    let publisher = ScanDataPublisher.shared
    
    func makeUIViewController(context: Context) -> VNDocumentCameraViewController {
        let vc = VNDocumentCameraViewController()
        vc.delegate = context.coordinator
        return vc
    }
    
    func updateUIViewController(_ uiViewController: VNDocumentCameraViewController, context: Context) {}
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, VNDocumentCameraViewControllerDelegate {
        let parent: DocumentCameraView
        
        init(parent: DocumentCameraView) {
            self.parent = parent
        }
        
        func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
            guard scan.pageCount > 0 else {
                controller.dismiss(animated: true)
                return
            }
            let image = scan.imageOfPage(at: 0)
            guard let imageData = image.jpegData(compressionQuality: 0.8) else {
                controller.dismiss(animated: true)
                return
            }
            // 发送扫描数据到Subject
            parent.publisher.scanDataSubject.send(imageData)
            controller.dismiss(animated: true)
        }
    }
}

在FolioView中订阅并处理:

struct FolioView: View {
    @Environment(\.modelContext) private var modelContext
    @State private var cancellable: AnyCancellable?
    
    var body: some View {
        Button("扫描名片") {
            let vc = UIHostingController(rootView: DocumentCameraView())
            UIApplication.shared.windows.first?.rootViewController?.present(vc, animated: true)
        }
        .onAppear {
            // 订阅扫描数据
            cancellable = ScanDataPublisher.shared.scanDataSubject.sink { imageData in
                self.addBusinessCard(imageData: imageData)
            }
        }
        .onDisappear {
            cancellable?.cancel()
        }
    }
    
    private func addBusinessCard(imageData: Data) {
        let newCard = BusinessCard(imageData: imageData)
        modelContext.insert(newCard)
        do {
            try modelContext.save()
        } catch {
            print("保存失败:\(error.localizedDescription)")
        }
    }
}

额外优化建议:

  • 扫描图片时压缩图片质量(比如示例中的jpegData(compressionQuality: 0.8)),减少Data体积,从源头降低开销。
  • 确保BusinessCard模型的imageData字段确实标记了@Attribute(.externalStorage),SwiftData会自动将大文件存储到外部容器,避免SQLite数据库膨胀。

内容的提问来源于stack exchange,提问作者Fil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 04:41:17