I have the following code. It simply run Vision API to get the text from the image. I use very simple GCD to dispatch the heavy Vision operation to the background queue, and then dispatch it back to main queue for completion
:
public struct TextRecognitionResult {
let observation: VNRecognizedTextObservation
let text: VNRecognizedText
let rect: CGRect
}
public enum TextRecognitionUtil {
private static let queue = DispatchQueue(label: "text_recognition", qos: .userInitiated)
public static func process(
image: UIImage,
recognitionLevel: VNRequestTextRecognitionLevel,
completion: @Sendable @escaping ([TextRecognitionResult]) -> Void)
{
guard let cgImage = image.cgImage else {
completion([])
return
}
let request = VNRecognizeTextRequest { (request, error) in
guard
error == nil,
let observations = request.results as? [VNRecognizedTextObservation]
else {
DispatchQueue.main.async {
completion([])
}
return
}
var results = [TextRecognitionResult]()
// Vision's origin is on bottom left
let transform = CGAffineTransform.identity
.scaledBy(x: 1, y: -1)
.translatedBy(x: 0, y: -image.size.height)
.scaledBy(x: image.size.width, y: image.size.height)
for observation in observations {
guard let text = observation.topCandidates(1).first else { continue }
let rect = observation.boundingBox.applying(transform)
results += [TextRecognitionResult(observation: observation, text: text, rect: rect)]
}
DispatchQueue.main.async {
completion(results)
}
}
request.recognitionLevel = recognitionLevel
self.queue.async {
let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
try? handler.perform([request])
}
}
}
This code violates Swift 6 concurrency, because TextRecognitionResult
is not Sendable
. The TextRecognitionResult
can’t be Sendable, because VNRecognizedTextObservation
and VNRecognizedText
is not Sendable, and they are both types defined in Vision that I cannot change.