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.
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.
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.
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 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.
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.
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 — 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.
CPFHub.io
Pronto para integrar a API?
50 consultas gratuitas para testar agora. Sem cartão de crédito. Acesso imediato à documentação.
Sobre a redação
Redação CPFHub.io
Time editorial especializado em APIs de CPF, identidade digital e compliance no mercado brasileiro. Produzimos guias técnicos, análises regulatórias e tutoriais sobre LGPD e KYC para desenvolvedores e líderes de produto.



