OCR sur iOS avec Apple Vision et Swift
Utilisez RecognizeTextRequest d’Apple Vision pour extraire le texte d’une image sur un iPhone. Ce
tutoriel crée une petite app SwiftUI qui ouvre une image depuis Fichiers et affiche un texte
sélectionnable. Il gère les photos pivotées, les résultats vides, les fichiers illisibles et
l’annulation lorsque l’utilisateur choisit une autre image ou quitte l’app.
Quelle API d’OCR iOS utiliser ?
Pour les images fixes, l’API Swift de Vision offre une reconnaissance de texte asynchrone sur iOS 18 et versions ultérieures. L’exemple ci-dessous utilise cette API avec du texte en anglais. Aucun SDK d’OCR tiers ni fichier de modèle téléchargé n’est nécessaire.
Si vous prenez en charge des versions antérieures d’iOS, étudiez plutôt
VNRecognizeTextRequest ;
le code ci-dessous n’est pas une couche de compatibilité. Pour une interface de caméra en direct,
consultez les recommandations sur VisionKit ci-dessous.
Reconnaître le texte d’une image fixe
Dans Xcode, créez un projet d’app iOS nommé ImageOCR, avec SwiftUI et Swift, sans
intégration de stockage. Définissez sa cible de déploiement sur iOS 18.0 et utilisez le mode de
langage Swift 6. Compilez avec
Xcode 16 ou version ultérieure
pour cette API. L’exemple a été compilé avec Xcode 27.0, le compilateur Swift 6.4 en mode Swift 6 et
le SDK iOS 27.0, puis exécuté sur un simulateur d’iPhone 17 sous iOS 27.0. iOS 18 est le minimum
requis par l’API, et non un environnement d’exécution testé ici.
Vous utiliserez trois fichiers : ajoutez OCR.swift, remplacez ContentView.swift et remplacez
ImageOCRApp.swift. Gardez les trois dans la cible de l’app. Il n’y a ni dépendance de paquet ni clé
d’autorisation pour la caméra et la photothèque à ajouter, car l’app sélectionne un fichier via le
sélecteur du système. Commencez par une image JPEG ou PNG locale contenant un texte anglais grand et
net. Si votre image se trouve dans Photos, enregistrez-la d’abord dans Fichiers depuis sa feuille de
partage.
Lire l’image et reconnaître son texte
Placez ce code dans OCR.swift. L’acteur de reconnaissance effectue la lecture du fichier et
le décodage de l’image en dehors de l’acteur principal. Il lit la première image du fichier et
transmet son
orientation Image I/O
à Vision. Un CGImage seul contient les pixels, mais pas l’orientation nécessaire pour les
afficher à l’endroit.
import Foundation
import ImageIO
import Observation
import Vision
enum ImageInputError: Error {
case unreadableImage
}
actor TextRecognizer {
func recognize(_ url: URL) async throws -> String {
try Task.checkCancellation()
let hasAccess = url.startAccessingSecurityScopedResource()
defer {
if hasAccess { url.stopAccessingSecurityScopedResource() }
}
let data = try Data(contentsOf: url)
guard let source = CGImageSourceCreateWithData(data as CFData, nil),
let image = CGImageSourceCreateImageAtIndex(source, 0, nil)
else {
throw ImageInputError.unreadableImage
}
let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil) as? [CFString: Any]
let rawOrientation = properties?[kCGImagePropertyOrientation] as? UInt32 ?? 1
let orientation = CGImagePropertyOrientation(rawValue: rawOrientation) ?? .up
try Task.checkCancellation()
var request = RecognizeTextRequest()
request.recognitionLevel = .accurate
request.recognitionLanguages = [Locale.Language(identifier: "en-US")]
request.usesLanguageCorrection = true
let observations = try await request.perform(on: image, orientation: orientation)
try Task.checkCancellation()
return observations.compactMap { $0.topCandidates(1).first?.string }
.joined(separator: "\n")
}
}
@MainActor
@Observable
final class OCRModel {
var text = ""
var message = "Choose an image to recognize."
var isRecognizing = false
private let recognizer = TextRecognizer()
private var work: Task<Void, Never>?
func importImage(_ result: Result<URL, Error>) {
cancel()
text = ""
guard case let .success(url) = result else {
message = "Could not open the selected file."
return
}
message = "Recognizing…"
isRecognizing = true
work = Task {
do {
let recognized = try await recognizer.recognize(url)
try Task.checkCancellation()
text = recognized
message = recognized.isEmpty ? "No text found." : "Text recognized."
} catch {
// A canceled task must not replace feedback from a newer selection.
guard !Task.isCancelled else { return }
message = "Could not read this image. Try a local JPEG or PNG."
}
isRecognizing = false
work = nil
}
}
func cancel() {
guard isRecognizing else { return }
work?.cancel()
work = nil
isRecognizing = false
message = "Recognition canceled."
}
}
Le modèle distingue une image valide sans texte reconnu d’un fichier qui n’a pas pu être lu. L’annulation est coopérative : elle empêche un résultat tardif de modifier l’écran, mais ne garantit pas que le traitement natif s’arrête immédiatement. Les modifications de l’interface restent sur l’acteur principal, sans suspension entre la dernière vérification d’annulation et la publication du résultat.
Connecter le sélecteur de fichiers et les résultats
Remplacez ContentView.swift par le code suivant. Le
contrat de fileImporter
exige un accès délimité pendant la lecture de l’URL sélectionnée ; TextRecognizer équilibre cet
accès avec defer. Une URL déjà située dans le bac à sable de l’app peut rester lisible
même lorsque le démarrage de l’accès délimité renvoie false ; c’est donc la lecture du
fichier qui détermine la réussite.
import SwiftUI
import UniformTypeIdentifiers
struct ContentView: View {
@Environment(\.scenePhase) private var scenePhase
@State private var model = OCRModel()
@State private var showImporter = false
var body: some View {
NavigationStack {
VStack(alignment: .leading, spacing: 16) {
HStack {
Button("Choose image") {
model.cancel()
showImporter = true
}
Button("Cancel recognition") { model.cancel() }
.disabled(!model.isRecognizing)
}
Text(model.message)
if model.isRecognizing {
ProgressView()
}
ScrollView {
Text(model.text)
.frame(maxWidth: .infinity, alignment: .leading)
.textSelection(.enabled)
}
}
.padding()
.navigationTitle("Image OCR")
}
.fileImporter(isPresented: $showImporter, allowedContentTypes: [.image]) { result in
model.importImage(result)
}
.onChange(of: scenePhase) { _, phase in
if phase != .active { model.cancel() }
}
.onDisappear { model.cancel() }
}
}
Remplacez ImageOCRApp.swift par le point d’entrée de l’app :
import SwiftUI
@main
struct ImageOCRApp: App {
var body: some Scene {
WindowGroup { ContentView() }
}
}
Lancez l’app et touchez Choose image. Accédez à votre image dans Fichiers et sélectionnez-la. L’app efface la transcription précédente, affiche Recognizing…, puis Text recognized. et le texte extrait. Appuyez longuement sur la transcription pour la sélectionner ou la copier. Une image vierge produit à la place No text found.. L’app conserve la transcription actuelle en mémoire ; elle n’enregistre aucun fichier de sortie et ne la téléverse pas.
Ouvrir le sélecteur et sélectionner un fichier sont deux actions distinctes. Depuis un écran
inactif, ouvrir puis fermer le sélecteur conserve le résultat précédent. Pendant la reconnaissance,
toucher Choose image annule immédiatement la tâche en
attente. Fermer ce sélecteur laisse Recognition canceled. affiché ;
sélectionner un fichier lance un nouveau traitement. Le système n’appelle pas le gestionnaire
d’achèvement de fileImporter en cas d’annulation.
Cancel recognition annule aussi le traitement en attente. L’app annule le travail lorsque sa scène devient inactive, y compris lors du passage en arrière-plan. Revenir dans l’app ne relance pas la reconnaissance : choisissez à nouveau l’image. Le texte déjà reconnu reste visible au retour dans l’app, à condition que le processus soit resté actif.
Vérifier le résultat et les états d’échec
Essayez une image simple contenant SWIFT VISION 2468 avant de tester un reçu complexe. Cet exemple
a reconnu ce texte sur le simulateur, y compris lorsque ses pixels étaient pivotés et que le fichier
portait les métadonnées d’orientation correspondantes. L’OCR peut tout de même mal lire de vrais
documents ; les observations jointes par des retours à la ligne ne reconstituent ni les tableaux ni
la mise en page.
| Entrée ou action | Comportement attendu |
|---|---|
| Texte anglais net | Une transcription sélectionnable et Text recognized. |
| Image vierge | No text found. avec une transcription vide |
| Image endommagée ou fichier qui disparaît avant la lecture | Could not read this image. Try a local JPEG or PNG. |
| Le sélecteur signale une erreur d’importation | Could not open the selected file. |
| Annuler ou remplacer un traitement en attente | L’ancienne tâche ne peut pas publier de résultat par-dessus le nouveau retour |
Pour un fichier illisible, essayez d’abord de le copier dans un dossier local de Fichiers, puis de le rouvrir. Un fournisseur de fichiers dans le cloud peut avoir besoin d’une connexion pour fournir les octets, même si la reconnaissance elle-même s’exécute localement. Cet exemple lit l’image entière en mémoire ; redimensionnez les entrées exceptionnellement volumineuses avant d’utiliser cet exemple comme composant de traitement par lots.
Numériser du texte depuis la caméra en direct
DataScannerViewController
est l’interface de caméra de VisionKit qui reconnaît le texte et les codes. Avant de la proposer,
vérifiez à la fois isSupported et isAvailable, fournissez NSCameraUsageDescription, et gérez le cas
où la numérisation devient indisponible pendant l’exécution de l’app. Conservez le sélecteur
d’images fixes comme solution de repli.
Une fonctionnalité de caméra en direct doit être testée sur un appareil physique compatible. En particulier, refusez l’accès à la caméra, accordez-le dans Réglages, puis revenez au processus existant de l’app pour vérifier la reprise. Testez également l’interruption et la fermeture pendant la numérisation. L’essai sur simulateur ci-dessus ne valide aucun de ces comportements de la caméra ; suivez le guide d’intégration d’Apple mis en lien lorsque vous ajoutez cette fonctionnalité distincte.
Transférer l’OCR par lots vers un flux de travail Transloadit
L’exemple ci-dessus sert une seule interaction dans une app en cours d’exécution. Si vos entrées relèvent déjà d’un flux de travail de traitement des téléversements, consultez la documentation de Transloadit sur l’OCR d’images (English) pour la voie côté serveur. Il s’agit d’une intégration distincte de cette app locale. Conservez les secrets du service sur votre backend, en dehors du bundle iOS.
Améliorer la précision de l’OCR
Commencez par l’orientation, la mise au point et une taille de texte lisible. Recadrer pour retirer les zones sans intérêt peut faciliter la reconnaissance d’un document ; agrandir une minuscule image source ne peut pas restituer les détails manquants. Pour les photos, réduisez les reflets et les perspectives prononcées avant d’ajuster les réglages de reconnaissance.
Si une page dense ne produit aucun texte, vérifiez
minimumTextHeightFraction.
Sa valeur par défaut est de 1/32 de la hauteur de l’image ; les petits caractères peuvent donc être
exclus même lorsqu’ils semblent nets. Recadrez sur une zone plus petite ou abaissez ce seuil ; la
prise en compte de textes plus petits peut augmenter le temps de reconnaissance et l’utilisation de
la mémoire.
L’exemple sélectionne l’anglais et .accurate. Pour d’autres langues, examinez
supportedRecognitionLanguages
de la requête et définissez recognitionLanguages selon vos entrées. Vision expose aussi customWords
pour du vocabulaire comme les noms de produits. Testez la correction linguistique sur vos propres
identifiants : un mot plausible n’est pas forcément le bon numéro de série.
Questions fréquentes
Quelle est la meilleure bibliothèque d’OCR pour iOS ?
Commencez par Apple Vision lorsque sa couverture linguistique convient à vos images et à votre cible de déploiement. Cela évite d’ajouter un binaire d’OCR natif tiers. Si vous avez besoin d’un modèle personnalisé ou d’une langue non prise en charge, évaluez ce besoin avec des images représentatives avant de choisir un autre moteur.
Faut-il utiliser SwiftyTesseract dans une nouvelle app iOS ?
SwiftyTesseract est archivé, et son mainteneur indique qu’il ne recevra plus de mises à jour. Ne le considérez pas comme une dépendance maintenue pour une nouvelle app. Une intégration existante nécessite ses propres tests de migration et de compatibilité.
L’OCR sur iOS nécessite-t-il une connexion réseau ?
Vision effectue la reconnaissance de texte sur l’appareil. Cette app ne comporte aucune étape de téléchargement de modèle ni de configuration de serveur. Hors connexion, choisissez un fichier déjà stocké localement ; télécharger un fichier depuis iCloud ou un autre fournisseur est une opération distincte.
Comment tester l’OCR ?
Testez à la fois la reconnaissance et le parcours de l’app qui l’entoure. Incluez un texte connu, des fichiers vierges et endommagés, des pixels pivotés avec métadonnées d’orientation, l’annulation, le remplacement et les transitions entre arrière-plan et premier plan. Utilisez vos langues, polices et mises en page réelles pour les tests d’acceptation. Vérifiez les champs obligatoires et l’utilité du résultat, plutôt que d’attendre une transcription identique à chaque version du système.
