# Como Integrar Validação de CPF em um App iOS com SwiftUI

> Aprenda a integrar validação de CPF em um app iOS usando SwiftUI com a API do CPFHub.io. Guia completo com MVVM, formulários e feedback visual.

**Publicado:** 18/12/2024
**Autor:** Redação CPFHub.io
**URL:** https://cpfhub.io/blog/validacao-cpf-ios-swiftui

---


Para integrar validação de CPF em um app iOS com SwiftUI, use o padrão MVVM: o `CPFViewModel` gerencia o estado e faz a chamada `GET` a `https://api.cpfhub.io/cpf/{CPF}` com `async/await` e o header `x-api-key`, enquanto a `CPFFormView` exibe o formulário declarativo e reage automaticamente às mudanças de estado. O resultado é uma experiência de validação em tempo real com feedback visual imediato.

## Introdução

SwiftUI transformou o desenvolvimento iOS com sua abordagem declarativa para construção de interfaces. Integrar a validação de CPF em um app SwiftUI significa combinar a reatividade do framework com chamadas assíncronas à API do CPFHub.io, tudo seguindo o padrão **MVVM** (Model-View-ViewModel).

## Arquitetura MVVM para consulta de CPF

O padrão MVVM separa a lógica de negócio da interface, facilitando testes e manutenção. O ViewModel será a ponte entre a API e a View.

```swift
import Foundation
import SwiftUI

@MainActor
class CPFViewModel: ObservableObject {
 @Published var cpfInput: String = ""
 @Published var resultado: CPFData?
 @Published var erro: String?
 @Published var isLoading: Bool = false

 var cpfFormatado: String {
 let numeros = cpfInput.filter { $0.isNumber }
 var resultado = ""
 for (index, char) in numeros.prefix(11).enumerated() {
 if index == 3 || index == 6 { resultado.append(".") }
 if index == 9 { resultado.append("-") }
 resultado.append(char)
 }
 return resultado
 }

 private let service = CPFService(
 apiKey: Bundle.main.object(
 forInfoDictionaryKey: "CPFHUB_API_KEY"
 ) as? String ?? ""
 )

 func consultar() async {
 let numeros = cpfInput.filter { $0.isNumber }
 guard numeros.count == 11 else {
 erro = "CPF deve conter 11 dígitos"
 return
 }

 isLoading = true
 erro = nil
 resultado = nil

 do {
 resultado = try await service.consultarCPF(numeros)
 } catch let error as CPFError {
 erro = error.localizedDescription
 } catch {
 erro = "Erro inesperado. Tente novamente."
 }

 isLoading = false
 }
}
```

| Componente | Responsabilidade | Propriedade |
|-----------|-----------------|-------------|
| **Model** | Dados e regras de negócio | CPFData, CPFResponse |
| **ViewModel** | Estado e lógica de apresentação | CPFViewModel |
| **View** | Interface declarativa | CPFFormView |

- **@MainActor** -- garante que todas as atualizações de UI aconteçam na thread principal
- **@Published** -- propriedades observáveis que atualizam a View automaticamente quando mudam
- **Bundle.main** -- forma segura de armazenar a API key no Info.plist sem hardcode no código

---

## Construindo o formulário de consulta

A View em SwiftUI é declarativa e reage automaticamente às mudanças do ViewModel.

```swift
import SwiftUI

struct CPFFormView: View {
 @StateObject private var viewModel = CPFViewModel()

 var body: some View {
 NavigationStack {
 Form {
 Section("Digite o CPF") {
 TextField("000.000.000-00", text: $viewModel.cpfInput)
 .keyboardType(.numberPad)
 .onChange(of: viewModel.cpfInput) { _, newValue in
 viewModel.cpfInput = String(
 newValue.filter { $0.isNumber }.prefix(11)
 )
 }

 Text(viewModel.cpfFormatado)
 .foregroundStyle(.secondary)
 .font(.caption)
 }

 Section {
 Button(action: {
 Task { await viewModel.consultar() }
 }) {
 HStack {
 if viewModel.isLoading {
 ProgressView()
 .padding(.trailing, 8)
 }
 Text(viewModel.isLoading ? "Consultando..." : "Validar CPF")
 }
 .frame(maxWidth: .infinity)
 }
 .disabled(viewModel.isLoading || viewModel.cpfInput.count < 11)
 }

 if let erro = viewModel.erro {
 Section {
 Text(erro)
 .foregroundStyle(.red)
 }
 }

 if let dados = viewModel.resultado {
 CPFResultadoView(dados: dados)
 }
 }
 .navigationTitle("Consulta de CPF")
 }
 }
}
```

- **@StateObject** -- cria e gerencia o ciclo de vida do ViewModel dentro da View
- **$viewModel.cpfInput** -- binding bidirecional que conecta o TextField ao estado do ViewModel
- **Task** -- cria uma tarefa assíncrona para chamar a função async do ViewModel

---

## Exibindo os resultados com componentes reutilizáveis

Separe a exibição dos resultados em um componente próprio para manter o código organizado.

```swift
import SwiftUI

struct CPFResultadoView: View {
 let dados: CPFData

 var body: some View {
 Section("Resultado da Consulta") {
 LabeledContent("Nome", value: dados.name)
 LabeledContent("CPF", value: formatarCPFExibicao(dados.cpf))
 LabeledContent("Nascimento", value: dados.birthDate)
 LabeledContent("Sexo", value: dados.gender == "M" ? "Masculino" : "Feminino")
 }
 }

 private func formatarCPFExibicao(_ cpf: String) -> String {
 let n = cpf.filter { $0.isNumber }
 guard n.count == 11 else { return cpf }
 let i = n.startIndex
 let p1 = n[i..<n.index(i, offsetBy: 3)]
 let p2 = n[n.index(i, offsetBy: 3)..<n.index(i, offsetBy: 6)]
 let p3 = n[n.index(i, offsetBy: 6)..<n.index(i, offsetBy: 9)]
 let p4 = n[n.index(i, offsetBy: 9)..<n.index(i, offsetBy: 11)]
 return "\(p1).\(p2).\(p3)-\(p4)"
 }
}

struct CPFResultadoView_Previews: PreviewProvider {
 static var previews: some View {
 Form {
 CPFResultadoView(dados: CPFData(
 cpf: "12345678909",
 name: "João da Silva",
 nameUpper: "JOÃO DA SILVA",
 gender: "M",
 birthDate: "01/01/1990",
 day: "01",
 month: "01",
 year: "1990"
 ))
 }
 }
}
```

- **LabeledContent** -- componente nativo do SwiftUI que exibe pares label/valor com alinhamento automático
- **PreviewProvider** -- permite visualizar o componente no Xcode Canvas sem executar o app
- **Componentização** -- separar em views menores facilita reutilização e testes

A documentação oficial da Apple sobre [SwiftUI e gerenciamento de estado](https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app) detalha os padrões recomendados para `@StateObject` e `@ObservedObject`.

---

## Adicionando validação em tempo real

Para uma experiência mais fluida, valide o CPF conforme o usuário digita usando **Combine** ou o modificador `onChange`.

```swift
import SwiftUI

struct CPFRealtimeView: View {
 @StateObject private var viewModel = CPFViewModel()
 @State private var validacaoLocal: String = ""

 var body: some View {
 Form {
 Section("CPF") {
 TextField("Digite o CPF", text: $viewModel.cpfInput)
 .keyboardType(.numberPad)
 .onChange(of: viewModel.cpfInput) { _, newValue in
 let numeros = newValue.filter { $0.isNumber }
 viewModel.cpfInput = String(numeros.prefix(11))
 validarLocalmente(numeros)
 }

 if !validacaoLocal.isEmpty {
 Text(validacaoLocal)
 .font(.caption)
 .foregroundStyle(
 validacaoLocal.contains("OK") ? .green : .orange
 )
 }
 }

 Section {
 Button("Consultar na API") {
 Task { await viewModel.consultar() }
 }
 .disabled(!validacaoLocal.contains("OK") || viewModel.isLoading)
 }
 }
 }

 private func validarLocalmente(_ cpf: String) {
 if cpf.count < 11 {
 validacaoLocal = "Faltam \(11 - cpf.count) dígitos"
 } else if cpf.isCPFValid {
 validacaoLocal = "Formato OK - pronto para consultar"
 } else {
 validacaoLocal = "Dígitos verificadores inválidos"
 }
 }
}
```

| Estado | Feedback ao usuário | Cor |
|--------|-------------------|-----|
| Menos de 11 dígitos | "Faltam X dígitos" | Laranja |
| 11 dígitos, formato válido | "Formato OK" | Verde |
| 11 dígitos, formato inválido | "Dígitos verificadores inválidos" | Laranja |
| Erro na API | Mensagem do erro | Vermelho |

- **Validação progressiva** -- feedback instantâneo conforme o usuário digita melhora a experiência
- **Botão desabilitado** -- só permite consulta à API quando o CPF passa na validação local
- **Separação de validações** -- local (formato) e remota (API) são etapas distintas e complementares

---

## Perguntas frequentes

### Como armazenar a API key do CPFHub.io de forma segura em um app SwiftUI?

Adicione a chave como variável no `Info.plist` via `Build Settings > User-Defined` e acesse via `Bundle.main.object(forInfoDictionaryKey:)`. Nunca inclua a chave diretamente no código-fonte. Para maior segurança, armazene-a no Keychain após o primeiro acesso e considere um backend proxy para não expor a chave no binário do app.

### A API retorna HTTP 429 quando o limite de consultas é atingido?

Não. A CPFHub.io nunca bloqueia requisições nem retorna HTTP 429. Ao atingir o limite do plano, cada consulta adicional é cobrada a R$0,15. O plano gratuito inclui 50 consultas mensais sem cartão de crédito; o plano Pro oferece 1.000 consultas por R$149/mês. Seu código SwiftUI não precisa tratar o status 429.

### Como testar o ViewModel sem fazer chamadas reais à API?

Use injeção de dependência no `CPFService` para substituir a `URLSession` por uma versão mockada em testes unitários. Crie um protocolo `CPFServiceProtocol` com o método `consultarCPF`, implemente um `MockCPFService` nos testes e injete-o no `CPFViewModel`. Assim você testa todos os estados (loading, sucesso, erro) sem consumir cota da API.

### Qual é a latência da API em um app iOS em produção?

A latência média da API CPFHub.io é de ~900ms. Em apps iOS, considere exibir um `ProgressView` enquanto a consulta ocorre para evitar a percepção de travamento. O padrão `isLoading: Bool` no ViewModel já cobre esse caso, e a validação local prévia garante que apenas CPFs com formato correto são enviados à API.

### Leia também

- [Como consumir a API de CPF em Swift usando URLSession](https://cpfhub.io/blog/consumir-api-cpf-swift-urlsession)
- [Como consumir API de CPF em Flutter com Dart e pacote http](https://cpfhub.io/blog/como-consumir-api-de-cpf-em-flutter-com-dart-e-pacote-http)
- [Como consumir API de CPF em React Native para apps mobile nativos](https://cpfhub.io/blog/como-consumir-api-cpf-react-native-apps-mobile-nativos)
- [API de CPF grátis para desenvolvedores: como começar em 5 minutos](https://cpfhub.io/blog/api-cpf-gratis-desenvolvedores-comecar-5-minutos)

---

## Conclusão

Integrar validação de CPF em um app iOS com SwiftUI é uma experiência fluida quando se combina a reatividade do framework com o padrão MVVM e chamadas assíncronas via async/await. A validação em tempo real fornece feedback imediato, enquanto a consulta à API traz dados reais para confirmar a identidade. Com componentes reutilizáveis e tratamento robusto de erros, seu app estará pronto para produção.

Cadastre-se em [cpfhub.io](https://www.cpfhub.io/) — 50 consultas mensais gratuitas, sem cartão de crédito — e comece a integrar a validação de CPF no seu app iOS hoje, com latência de ~900ms e zero configuração de servidor.

