OCR en iOS con Apple Vision y Swift
Usa RecognizeTextRequest de Apple Vision para extraer texto de una imagen en un iPhone.
Esta guía crea una pequeña app de SwiftUI que abre una imagen desde Archivos y muestra texto
seleccionable. Gestiona fotos rotadas, resultados vacíos, archivos ilegibles y la cancelación cuando
el usuario elige otra imagen o sale de la app.
¿Qué API de OCR para iOS deberías usar?
Para imágenes estáticas, la API de Swift de Vision ofrece reconocimiento de texto asíncrono en iOS 18 y versiones posteriores. El siguiente ejemplo usa esta API y texto en inglés. No se necesita un SDK de OCR de terceros ni descargar un archivo de modelo.
Si admites versiones anteriores de iOS, investiga
VNRecognizeTextRequest
como alternativa; el código siguiente no es una capa de compatibilidad. Para una interfaz de
cámara en vivo, consulta
las indicaciones sobre VisionKit más abajo.
Reconoce texto en una imagen estática
Crea un proyecto de app para iOS en Xcode llamado ImageOCR, con SwiftUI y Swift,
sin integración de almacenamiento. Establece iOS 18.0 como versión mínima de despliegue y usa el
modo de lenguaje Swift 6. Compila con
Xcode 16 o posterior
para esta API. El ejemplo se compiló con Xcode 27.0, el compilador Swift 6.4 en modo Swift 6 y el SDK
de iOS 27.0; después se ejecutó en un simulador de iPhone 17 con iOS 27.0. iOS 18 es la versión mínima
para la API, no un entorno de ejecución probado aquí.
Usarás tres archivos: añade OCR.swift, reemplaza ContentView.swift
y reemplaza ImageOCRApp.swift. Mantén los tres en el target de la app. No hay dependencias
de paquetes ni claves de permisos de cámara o fototeca que añadir, porque la app selecciona un
archivo mediante el selector del sistema. Empieza con un JPEG o PNG local que contenga texto grande
y claro en inglés. Si tu imagen está en Fotos, usa su menú para compartir y guárdala primero en
Archivos.
Lee la imagen y reconoce su texto
Coloca esto en OCR.swift. El actor de reconocimiento mantiene la lectura del
archivo y la decodificación de la imagen fuera del actor principal. Lee la primera imagen del
archivo y pasa su
orientación de Image I/O
a Vision. Un CGImage por sí solo contiene los píxeles, no la orientación
necesaria para mostrarlos en la posición correcta.
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."
}
}
El modelo distingue una imagen válida sin texto reconocido de un archivo que no se pudo leer. La cancelación es cooperativa: impide que un resultado tardío cambie la pantalla, pero no garantiza que el procesamiento nativo se detenga de inmediato. Los cambios de la interfaz permanecen en el actor principal, sin suspensión entre la comprobación final de cancelación y la publicación del resultado.
Conecta el selector de Archivos y los resultados
Reemplaza ContentView.swift por lo siguiente. El
contrato de fileImporter
requiere acceso de ámbito limitado mientras se lee la URL seleccionada;
TextRecognizer equilibra ese acceso con defer.
Una URL que ya esté dentro del entorno aislado de la app puede seguir siendo legible cuando el
inicio del acceso de ámbito limitado devuelve false, por lo que la lectura
del archivo determina si la operación tiene éxito.
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() }
}
}
Reemplaza ImageOCRApp.swift por el punto de entrada de la app:
import SwiftUI
@main
struct ImageOCRApp: App {
var body: some Scene {
WindowGroup { ContentView() }
}
}
Ejecuta la app y toca Choose image. Busca tu imagen en Archivos y selecciónala. La app borra la transcripción anterior, muestra Recognizing… y luego muestra Text recognized. y el texto extraído. Mantén presionada la transcripción para seleccionarla o copiarla. Una imagen en blanco produce, en cambio, No text found.. La app conserva la transcripción actual en memoria; no guarda un archivo de salida ni lo sube.
Abrir el selector y seleccionar un archivo son acciones separadas. Si no hay ningún reconocimiento en curso,
abrir y luego cerrar el selector conserva el resultado anterior. Durante el reconocimiento, tocar
Choose image cancela de inmediato la tarea pendiente.
Al cerrar ese selector, queda Recognition canceled.;
seleccionar un archivo inicia una nueva tarea. El sistema no llama al controlador de finalización
de fileImporter cuando se cancela.
Cancel recognition también cancela las tareas pendientes. La app cancela cuando su escena queda inactiva, incluso durante la transición al segundo plano. Volver a la app no reinicia el reconocimiento: elige la imagen de nuevo. El texto completado sigue visible al volver a la app, siempre que el proceso haya permanecido activo.
Comprueba el resultado y los estados de error
Prueba una imagen sencilla que contenga SWIFT VISION 2468 antes de probar un recibo
complejo. Este ejemplo reconoció ese texto en el simulador, incluso cuando sus píxeles estaban
rotados y el archivo contenía los metadatos de orientación correspondientes. El OCR aún puede
interpretar mal documentos reales; las observaciones unidas por saltos de línea no reconstruyen
las tablas ni el diseño de la página.
| Entrada o acción | Comportamiento esperado |
|---|---|
| Texto claro en inglés | Una transcripción seleccionable y Text recognized. |
| Imagen en blanco | No text found. con una transcripción vacía |
| Imagen dañada o archivo que desaparece antes de leerlo | Could not read this image. Try a local JPEG or PNG. |
| El selector informa de un error de importación | Could not open the selected file. |
| Cancelar o reemplazar una tarea pendiente | La tarea anterior no puede publicar un resultado que sobrescriba la nueva información de estado |
Si un archivo es ilegible, prueba primero a copiarlo a una carpeta local de Archivos y volver a abrirlo. Un proveedor de archivos en la nube puede necesitar conexión para entregar los bytes, aunque el reconocimiento en sí se ejecute localmente. Este ejemplo lee toda la imagen en memoria; reduce el tamaño de las entradas inusualmente grandes antes de usarlo como componente de procesamiento por lotes.
Escanea texto desde la cámara en vivo
DataScannerViewController
es la interfaz de cámara de VisionKit para reconocer texto y códigos. Antes de ofrecerla, comprueba
tanto isSupported como isAvailable, proporciona
NSCameraUsageDescription y gestiona los casos en que el escaneo deje de estar disponible mientras
la app se ejecuta. Conserva el selector de imágenes estáticas como alternativa.
Una función de cámara en vivo necesita pruebas en hardware físico compatible. En particular, deniega el acceso a la cámara, concédelo en Configuración y vuelve al proceso existente de la app para comprobar la recuperación. Prueba también las interrupciones y el cierre durante el escaneo. El ejercicio anterior en el simulador no verifica ninguno de esos comportamientos de la cámara; usa la guía de integración de Apple enlazada al añadir esa función independiente.
Traslada el OCR por lotes a un flujo de trabajo de Transloadit
El ejemplo anterior atiende una interacción en una app en ejecución. Si tus entradas ya forman parte de un flujo de subida y procesamiento, considera la documentación de OCR de imágenes de Transloadit para la vía del lado del servidor. Es una integración independiente de esta app local. Mantén los secretos del servicio en tu backend, fuera del paquete de la app de iOS.
Mejora la precisión del OCR
Empieza por la orientación, el enfoque y un tamaño de texto legible. Recortar el entorno irrelevante puede facilitar el reconocimiento de un documento; ampliar una imagen de origen diminuta no puede restaurar los detalles que faltan. En las fotografías, reduce los reflejos y la perspectiva pronunciada antes de ajustar la configuración del reconocimiento.
Si una página densa no produce texto, comprueba
minimumTextHeightFraction.
Su valor predeterminado es 1/32 de la altura de la imagen, por lo que la letra pequeña puede quedar
excluida incluso si se ve clara. Recorta una región más pequeña o reduce ese umbral; considerar
texto más pequeño puede aumentar el tiempo de reconocimiento y el uso de memoria.
El ejemplo selecciona inglés y .accurate. Para otros idiomas, inspecciona
supportedRecognitionLanguages
de la solicitud y establece recognitionLanguages según tu entrada. Vision también expone
customWords para vocabulario como nombres de productos. Prueba la corrección
lingüística con tus propios identificadores: una palabra plausible no es necesariamente el número
de serie correcto.
Preguntas frecuentes
¿Cuál es la mejor biblioteca de OCR para iOS?
Empieza con Apple Vision si su cobertura de idiomas se ajusta a tus imágenes y a la versión mínima de despliegue. Así evitas añadir un binario nativo de OCR de terceros. Si necesitas un modelo personalizado o un idioma no compatible, evalúa ese requisito con imágenes representativas antes de elegir otro motor.
¿Debería usar SwiftyTesseract en una nueva app de iOS?
SwiftyTesseract está archivado, y su responsable indica que no recibirá más actualizaciones. No lo consideres una dependencia con mantenimiento para una app nueva. Una integración existente necesita sus propias pruebas de migración y compatibilidad.
¿El OCR de iOS requiere una conexión de red?
Vision realiza el reconocimiento de texto en el dispositivo. Esta app no tiene ningún paso de descarga de modelos ni de configuración de un servidor. Elige un archivo que ya esté almacenado localmente cuando trabajes sin conexión; descargar un archivo de iCloud u otro proveedor es una operación independiente.
¿Cómo debería probar el OCR?
Ejecuta tanto el reconocimiento como el flujo de la app que lo rodea. Incluye texto conocido, archivos en blanco y dañados, píxeles rotados con metadatos de orientación, cancelación, reemplazo y transiciones entre segundo y primer plano. Usa tus idiomas, fuentes y diseños reales para las pruebas de aceptación. Comprueba los campos obligatorios y que la salida sea útil, en lugar de esperar una transcripción exacta idéntica en todas las versiones del sistema operativo.
