OCR no iOS com Apple Vision e Swift
Use o RecognizeTextRequest do Apple Vision para extrair texto de uma imagem em um iPhone. Este passo a passo
cria um pequeno app SwiftUI que abre uma imagem do Arquivos e exibe texto selecionável. Ele lida com
fotos rotacionadas, resultados vazios, arquivos ilegíveis e cancelamento quando o usuário escolhe
outra imagem ou sai do app.
Qual API de OCR do iOS você deve usar?
Para imagens estáticas, a API Swift do Vision oferece reconhecimento de texto assíncrono no iOS 18 e posteriores. O exemplo abaixo usa essa API e texto em inglês. Não é preciso nenhum SDK de OCR de terceiros nem arquivo de modelo baixado.
Se você oferece suporte a versões anteriores do iOS, investigue
VNRecognizeTextRequest
em vez disso; o código abaixo não é um wrapper de compatibilidade. Para uma interface de câmera ao
vivo, consulte as orientações sobre o VisionKit abaixo.
Reconhecer texto em uma imagem estática
Crie no Xcode um projeto de app iOS chamado ImageOCR, usando SwiftUI e Swift, sem integração de
armazenamento. Defina o deployment target como iOS 18.0 e use o modo de linguagem Swift 6. Compile
com o Xcode 16 ou posterior
para usar essa API. O exemplo foi compilado com o Xcode 27.0, o compilador Swift 6.4 no modo Swift 6
e o SDK do iOS 27.0, e depois executado em um simulador de iPhone 17 com iOS 27.0. O iOS 18 é o
mínimo exigido pela API, não um runtime testado aqui.
Você usará três arquivos: adicione OCR.swift, substitua ContentView.swift e substitua
ImageOCRApp.swift. Mantenha os três no target do app. Não há dependências de pacotes nem chaves de
permissão de câmera e de fototeca para adicionar, porque o app seleciona um arquivo pelo seletor do
sistema. Comece com um JPEG ou PNG local que contenha texto em inglês grande e nítido. Se a imagem
estiver no Fotos, use o menu de compartilhamento dela para salvá-la no Arquivos primeiro.
Ler a imagem e reconhecer o texto
Coloque isto em OCR.swift. O actor de reconhecimento mantém a leitura do arquivo e a
decodificação da imagem fora do main actor. Ele lê a primeira imagem do arquivo e passa a
orientação do Image I/O
dela para o Vision. Um CGImage sozinho contém os pixels, não a orientação necessária para
exibi-los na posição correta.
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."
}
}
O modelo distingue uma imagem válida sem texto reconhecido de um arquivo que não pôde ser lido. O cancelamento é cooperativo: ele impede que um resultado tardio altere a tela, mas não garante que o processamento nativo pare imediatamente. As alterações de UI ficam no main actor, sem suspensão entre a verificação final de cancelamento e a publicação do resultado.
Conectar o seletor do Arquivos aos resultados
Substitua ContentView.swift pelo código a seguir. O
contrato de fileImporter
exige acesso com escopo durante a leitura da URL selecionada; TextRecognizer equilibra esse acesso
com defer. Uma URL que já está dentro do sandbox do app ainda pode ser legível mesmo quando
o início do acesso com escopo retorna false, então é a leitura do arquivo que determina o
sucesso.
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() }
}
}
Substitua ImageOCRApp.swift pelo ponto de entrada do app:
import SwiftUI
@main
struct ImageOCRApp: App {
var body: some Scene {
WindowGroup { ContentView() }
}
}
Execute o app e toque em Choose image. Navegue até sua imagem no Arquivos e selecione-a. O app limpa a transcrição anterior, mostra Recognizing… e depois exibe Text recognized. e o texto extraído. Mantenha a transcrição pressionada para selecioná-la ou copiá-la. Já uma imagem em branco produz No text found.. O app mantém a transcrição atual na memória; ele não salva um arquivo de saída nem faz upload da transcrição.
Abrir o seletor e selecionar um arquivo são ações separadas. A partir de uma tela ociosa, abrir e
depois dispensar o seletor mantém o resultado anterior. Durante o reconhecimento, tocar em
Choose image cancela imediatamente a tarefa pendente. Dispensar
esse seletor mantém Recognition canceled. na tela; selecionar um
arquivo inicia um novo trabalho. O sistema não chama o completion handler de fileImporter no
cancelamento.
Cancel recognition também cancela o trabalho pendente. O app cancela quando a cena dele fica inativa, inclusive a caminho do segundo plano. Voltar ao app não reinicia o reconhecimento: escolha a imagem novamente. O texto já concluído continua visível quando você volta ao app, desde que o processo tenha continuado ativo.
Verificar o resultado e os estados de falha
Experimente uma imagem simples contendo SWIFT VISION 2468 antes de testar um recibo complexo. Esse
texto foi reconhecido por este exemplo no simulador, inclusive quando os pixels estavam rotacionados
e o arquivo trazia os metadados de orientação correspondentes. O OCR ainda pode ler documentos reais
incorretamente; as observações unidas por quebras de linha não são uma reconstrução de tabelas nem
do layout da página.
| Entrada ou ação | Comportamento esperado |
|---|---|
| Texto em inglês nítido | Uma transcrição selecionável e Text recognized. |
| Imagem em branco | No text found. com uma transcrição vazia |
| Imagem danificada ou arquivo que desaparece antes da leitura | Could not read this image. Try a local JPEG or PNG. |
| O seletor informa um erro de importação | Could not open the selected file. |
| Cancelar ou substituir o trabalho pendente | A tarefa antiga não consegue publicar um resultado por cima do novo feedback |
Para um arquivo ilegível, primeiro tente copiá-lo para uma pasta local no Arquivos e abri-lo novamente. Um provedor de arquivos na nuvem pode precisar de conectividade para entregar os bytes, mesmo que o reconhecimento em si seja executado localmente. Este exemplo lê a imagem inteira na memória; redimensione entradas excepcionalmente grandes antes de usá-lo como componente de processamento em lote.
Escanear texto com a câmera ao vivo
O DataScannerViewController
é a interface de câmera do VisionKit para reconhecer texto e códigos. Antes de oferecê-lo, verifique
tanto isSupported quanto isAvailable, forneça NSCameraUsageDescription e trate o caso de o
escaneamento ficar indisponível enquanto o app está em execução. Mantenha o seletor de imagens
estáticas como alternativa.
Um recurso de câmera ao vivo precisa ser testado em hardware físico compatível. Em particular, negue o acesso à câmera, conceda-o em Ajustes e volte ao processo do app já existente para verificar a recuperação. Teste também interrupções e o fechamento durante o escaneamento. O exercício no simulador acima não comprova nenhum desses comportamentos da câmera; use o guia de integração da Apple vinculado ao adicionar esse recurso separado.
Mover o OCR em lote para um fluxo de trabalho da Transloadit
O exemplo acima atende a uma interação em um app em execução. Se suas entradas já fazem parte de um fluxo de trabalho de processamento de uploads, considere a documentação de OCR de imagens da Transloadit para o caminho no lado do servidor. Essa é uma integração independente deste app local. Mantenha os segredos do serviço no seu backend, fora do bundle do iOS.
Melhorar a precisão do OCR
Comece pela orientação, pelo foco e por um tamanho de texto legível. Recortar o entorno irrelevante pode facilitar o reconhecimento de um documento; esticar uma imagem de origem minúscula não recupera detalhes ausentes. Em fotografias, reduza reflexos e perspectivas muito inclinadas antes de ajustar as configurações de reconhecimento.
Se uma página densa não produzir texto, verifique
minimumTextHeightFraction.
O padrão é 1/32 da altura da imagem, então letras pequenas podem ser excluídas mesmo quando parecem
nítidas. Recorte uma região menor ou reduza esse limite; considerar textos menores pode aumentar o
tempo de reconhecimento e o uso de memória.
O exemplo seleciona inglês e .accurate. Para outros idiomas, inspecione
supportedRecognitionLanguages
da requisição e defina recognitionLanguages de acordo com sua entrada. O Vision também expõe
customWords para vocabulário como nomes de produtos. Teste a correção de idioma com seus próprios
identificadores: uma palavra plausível não é necessariamente o número de série correto.
Perguntas frequentes
Qual é a melhor biblioteca de OCR para iOS?
Comece pelo Apple Vision quando a cobertura de idiomas dele atender às suas imagens e ao seu deployment target. Ele evita adicionar um binário nativo de OCR de terceiros. Se você precisar de um modelo personalizado ou de um idioma sem suporte, avalie esse requisito com imagens representativas antes de escolher outro mecanismo.
Devo usar o SwiftyTesseract em um novo app iOS?
O SwiftyTesseract está arquivado, e o mantenedor afirma que ele não receberá mais atualizações. Não o trate como uma dependência mantida em um app novo. Uma integração existente precisa de seus próprios testes de migração e de compatibilidade.
O OCR no iOS precisa de conexão de rede?
O Vision faz o reconhecimento de texto no dispositivo. Este app não tem etapa de download de modelo nem de configuração de servidor. Ao trabalhar offline, escolha um arquivo já armazenado localmente; baixar um arquivo do iCloud ou de outro provedor é uma operação separada.
Como devo testar o OCR?
Execute tanto o reconhecimento quanto o fluxo do app ao redor dele. Inclua texto conhecido, arquivos em branco e danificados, pixels rotacionados com metadados de orientação, cancelamento, substituição e transições entre segundo e primeiro plano. Use seus idiomas, fontes e layouts reais nos testes de aceitação. Verifique os campos obrigatórios e se a saída é útil, em vez de esperar uma transcrição exatamente igual em todas as versões do sistema operacional.
