iOS OCR with Apple Vision and Swift
For a new iOS OCR feature, start with Apple’s Vision framework rather than a Tesseract wrapper. The
Swift-native
RecognizeTextRequest
recognizes text in still images, while VisionKit’s
DataScannerViewController
provides a ready-made live-camera scanner for text and machine-readable codes.
That recommendation is a change from the original version of this guide. SwiftyTesseract’s own repository is archived and no longer maintained, and its maintainer recommends Apple Vision for supported languages.
Which iOS OCR API should you use?
| Requirement | Recommended path | Why |
|---|---|---|
| Recognize text in a captured or selected image | RecognizeTextRequest | You control image capture, request settings, and result handling. |
| Scan text continuously through the camera | DataScannerViewController | VisionKit supplies the camera UI, guidance, highlighting, and live updates. |
| Process uploads after they leave the device | Transloadit 🤖/image/ocr | The OCR step can run inside the same backend workflow as storage and other media processing. |
| Use a language or custom model unavailable in Vision | A maintained OCR engine or your own Tesseract integration | Validate language coverage, binary maintenance, and App Store packaging before committing. |
Use on-device Vision when recognition is part of an immediate iOS interaction. Use a backend OCR workflow when jobs must continue independently of the app, process many uploads, or feed the result into storage and other processing steps.
Recognize text in a still image
The following service accepts a CGImage, runs Apple Vision, and returns each observation’s
transcript. .accurate prioritizes recognition accuracy; use .fast when latency matters more.
This uses the Swift-native Vision API available to apps whose deployment target supports it.
import CoreGraphics
import Vision
final class TextRecognizer {
func recognizeText(in image: CGImage) async throws -> [String] {
var request = RecognizeTextRequest()
request.recognitionLevel = .accurate
request.usesLanguageCorrection = true
let observations = try await request.perform(on: image)
return observations.map { observation in
observation.transcript
}
}
}
If your deployment target includes older OS releases, use the original
VNRecognizeTextRequest
API behind the same application-level interface. Keeping the OCR call behind a small service makes
that compatibility choice independent of the rest of the UI.
Vision exposes additional controls for language selection, custom vocabulary, and minimum text height. Ask the active request for its supported recognition languages instead of hardcoding a language list that may differ by OS version and request revision.
If the image came from UIImage, narrow the optional Core Graphics representation before calling
the service:
enum ImageInputError: Error {
case missingCoreGraphicsImage
}
guard let cgImage = selectedImage.cgImage else {
throw ImageInputError.missingCoreGraphicsImage
}
let lines = try await TextRecognizer().recognizeText(in: cgImage)
let recognizedText = lines.joined(separator: "\n")
Run recognition work away from latency-sensitive UI code, then update interface state on the main actor.
Scan text from the live camera
For an interactive scanner, DataScannerViewController can recognize text directly from the camera
feed. Check both isSupported and isAvailable: hardware support alone does not guarantee that
scanning is currently available.
import UIKit
import VisionKit
@MainActor
final class ScannerViewController: UIViewController, DataScannerViewControllerDelegate {
private var scanner: DataScannerViewController?
func presentScanner() {
guard DataScannerViewController.isSupported,
DataScannerViewController.isAvailable
else {
return
}
let scanner = DataScannerViewController(
recognizedDataTypes: [.text()],
qualityLevel: .balanced,
recognizesMultipleItems: true,
isHighFrameRateTrackingEnabled: false,
isPinchToZoomEnabled: true,
isGuidanceEnabled: true,
isHighlightingEnabled: true
)
scanner.delegate = self
self.scanner = scanner
present(scanner, animated: true) {
try? scanner.startScanning()
}
}
func dataScanner(
_ dataScanner: DataScannerViewController,
didTapOn item: RecognizedItem
) {
guard case let .text(text) = item else {
return
}
print(text.transcript)
}
}
Add NSCameraUsageDescription to the app’s information property list with a clear explanation of
why camera access is required. Provide a still-image fallback when live scanning is unavailable.
Move batch OCR to a Transloadit workflow
On-device OCR is a good fit for immediate feedback, but it ties processing to the app session. For uploads that need durable, backend-controlled processing, add 🤖/image/ocr to an Assembly. The Robot supports AWS and GCP providers and can return JSON, plain text, or metadata for a later Step.
This Template extracts UTF-8 text from each uploaded image and stores both the original image and the OCR output:
{
"steps": {
":original": {
"robot": "/upload/handle"
},
"recognized": {
"use": ":original",
"robot": "/image/ocr",
"provider": "gcp",
"format": "text"
},
"exported": {
"use": [":original", "recognized"],
"robot": "/s3/store",
"credentials": "YOUR_S3_CREDENTIALS_NAME"
}
}
}
Keep your Transloadit Auth Secret out of the iOS bundle. Create signatures on your backend as described in the authentication documentation, then let the app upload with the signed parameters. See TransloaditKit for the iOS SDK surface, and use the live OCR demo to inspect the metadata workflow before integrating it.
The cloud providers behind the OCR Robot can update their models, so do not assert exact OCR text in long-lived tests. Evaluate representative documents, persist the provider and settings used, and design downstream code to tolerate recognition errors.
Improve OCR accuracy
- Capture the highest useful resolution without stretching a small source image.
- Correct orientation before recognition.
- Crop to the document or text region when the surrounding scene is irrelevant.
- Prefer even lighting and avoid glare, motion blur, and steep perspective.
- Set recognition languages deliberately when the likely language is known.
- Add domain-specific terms with
customWordswhen Vision otherwise corrects them incorrectly. - Keep confidence thresholds and human review proportional to the consequence of a wrong result.
Common questions
What is the best OCR library for iOS?
For supported languages and normal image text, Apple Vision is the default choice because it is a first-party framework and requires no third-party OCR binary. VisionKit is the higher-level choice for live camera scanning. Use another engine only after a concrete language, model, or deployment requirement rules those options out.
Should I use SwiftyTesseract in a new iOS app?
No. Its repository is archived and explicitly says that it will receive no further updates. An existing application may need a measured migration, but new work should not adopt it as a maintained dependency.
Does iOS OCR require a network connection?
Apple Vision recognition runs on the device. A Transloadit OCR Assembly is a network service and is appropriate when processing should continue in a backend workflow.
How should I test OCR?
Use a fixture set that represents your actual languages, fonts, camera conditions, and document layouts. Assert application-level acceptance criteria—such as required fields being present—rather than one exact transcript for every image.
