So I have this code:
fileprivate class DocumentScanDelegate: NSObject, VNDocumentCameraViewControllerDelegate {
static let shared = DocumentScanDelegate()
var compressionQuality: CGFloat = 1
var onScanSuccess: (UIImage) -> Void = { _ in }
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
controller.dismiss(animated: true)
guard scan.pageCount >= 1 else { return }
let lastPage = scan.imageOfPage(at: scan.pageCount - 1)
let compressed = lastPage.compressed(quality: compressionQuality)
onScanSuccess(compressed)
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {
controller.dismiss(animated: true)
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
controller.dismiss(animated: true)
}
}
I got 2 errors:
shared
:Static property 'shared' is not concurrency-safe because non-'Sendable' type 'DocumentScanDelegate' may have shared mutable state; this is an error in the Swift 6 language mode
controller.dismiss
calls:Call to main actor-isolated instance method 'dismiss(animated:completion:)' in a synchronous nonisolated context; this is an error in the Swift 6 language mode
Both makes sense. So what I did is adding @MainActor
to DocumentScanDelegate
. Then first warning disappeared. But the second warning becomes:
Main actor-isolated instance method 'documentCameraViewController(_:didFinishWith:)' cannot be used to satisfy nonisolated protocol requirement; this is an error in the Swift 6 language mode
Then I use @preconcurrency
to annotate VNDocumentCameraViewControllerDelegate
conformance:
@MainActor
fileprivate class DocumentScanDelegate: NSObject, @preconcurrency VNDocumentCameraViewControllerDelegate {
...
}
This @preconcurrency
trick is very similar to WWDC 2024's video (https://developer.apple.com/videos/play/wwdc2024/10169/?time=1520)
The error becomes:
Main actor-isolated instance method 'documentCameraViewController(_:didFinishWith:)' cannot be used to satisfy nonisolated protocol requirement; this is an error in the Swift 6 language mode
With the following sub-error:
Class 'VNDocumentCameraScan' does not conform to the 'Sendable' protocol
Then I use @preconcurrency
import:
@preconcurrency import VisionKit
The same warning persists.
There's also a new warning (why?):
'@preconcurrency' attribute on module 'VisionKit' has no effect
Following up on our conversation in the comments here.
Considering your last question, I'm not exactly sure why this works tbh, but I tried it with strict concurrency checking set to "complete" and it does not complain. My best guess is, that it's not an issue to capture controller
in the Task closure, because it's guaranteed to be accessed only on the @MainActor
there.
import VisionKit
private class DocumentScanDelegate: NSObject, VNDocumentCameraViewControllerDelegate {
@MainActor
static let shared = DocumentScanDelegate()
var compressionQuality: CGFloat = 1
var onScanSuccess: (UIImage) -> Void = { _ in }
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFinishWith scan: VNDocumentCameraScan) {
Task { @MainActor in
controller.dismiss(animated: true)
}
guard scan.pageCount >= 1 else { return }
let lastPage = scan.imageOfPage(at: scan.pageCount - 1)
let compressed = lastPage.compressed(quality: compressionQuality)
onScanSuccess(compressed)
}
func documentCameraViewController(_ controller: VNDocumentCameraViewController, didFailWithError error: Error) {
Task { @MainActor in
controller.dismiss(animated: true)
}
}
func documentCameraViewControllerDidCancel(_ controller: VNDocumentCameraViewController) {
Task { @MainActor in
controller.dismiss(animated: true)
}
}
}