Bienvenidos a un nuevo y extenso tutorial para nuestro blog tecnológico. Si eres un iOS Developer que busca crear aplicaciones no solo hermosas, sino increíblemente seguras y fáciles de usar, estás en el lugar correcto.
Vamos a abordar uno de los elementos más demandados por los usuarios modernos: la autenticación biométrica. Específicamente, vamos a aprender a fondo cómo implementar Face ID en SwiftUI (y por extensión, Touch ID). Atrás quedaron los días en los que obligábamos a nuestros usuarios a teclear contraseñas largas y complejas cada vez que abrían nuestra app. En la programación Swift actual, la biometría es el estándar de oro para la experiencia del usuario (UX) y la seguridad.
En este artículo, descubriremos cómo usar el framework LocalAuthentication para integrar esta tecnología. Además, crearemos un código en Swift completamente preparado para funcionar de manera nativa en iOS, macOS y iPadOS usando Xcode.
1. El Framework LocalAuthentication
En el ecosistema de Apple, no interactuamos directamente con los sensores de la cámara infrarroja del Face ID o el escáner dactilar del Touch ID. Por razones de seguridad extrema, esa información está resguardada en el Secure Enclave del procesador.
Para que nuestra app pueda preguntar “¿Es este el dueño del dispositivo?”, Apple nos proporciona el framework LocalAuthentication.
El flujo de trabajo en la programación Swift con este framework es simple pero estricto:
- Creamos un contexto de autenticación (
LAContext). - Verificamos si el dispositivo puede realizar la autenticación biométrica (¿Tiene el hardware? ¿Está configurado?).
- Solicitamos la evaluación de la política (el momento en el que aparece la animación de Face ID en la pantalla).
- Actuamos según el resultado (éxito o error).
2. Paso 0: Permisos en Xcode (El archivo Info.plist)
Antes de escribir una sola línea lógica de SwiftUI, hay un paso crítico que muchos desarrolladores olvidan, lo que resulta en un crasheo inmediato de la aplicación. Para usar Face ID en SwiftUI, el usuario debe saber por qué lo estás usando.
- Selecciona tu proyecto en Xcode.
- Ve a la pestaña Info de tu Target.
- Añade una nueva propiedad llamada
Privacy - Face ID Usage Description(su clave interna esNSFaceIDUsageDescription). - En el campo de valor (String), escribe una justificación clara. Por ejemplo: “Usamos Face ID para desbloquear tus notas secretas de forma rápida y segura.”
Nota para el iOS Developer: Touch ID no requiere una clave en el Info.plist, pero dado que hoy en día la mayoría de los iPhones usan Face ID, esta clave es absolutamente obligatoria.
3. Arquitectura de la Lógica: El BiometricManager
Para mantener nuestro código limpio y respetar el patrón MVVM (Model-View-ViewModel) que tan bien se lleva con SwiftUI, vamos a encapsular toda la lógica de LocalAuthentication en una clase dedicada.
Crearemos una clase observable usando la concurrencia moderna de Swift (async/await), que hace que el código sea infinitamente más legible que los antiguos completion handlers.
Crea un nuevo archivo de Swift llamado BiometricManager.swift.
import Foundation
import LocalAuthentication
// Usamos @MainActor para asegurar que todos los cambios de estado
// que afecten a la UI en SwiftUI ocurran en el hilo principal.
@MainActor
class BiometricManager: ObservableObject {
// Estados que nuestra vista de SwiftUI observará
@Published var isUnlocked: Bool = false
@Published var errorMessage: String? = nil
// Método principal de autenticación
func authenticate() async {
let context = LAContext()
var error: NSError?
// 1. Definimos la política: Queremos usar biometría
let policy: LAPolicy = .deviceOwnerAuthenticationWithBiometrics
// El mensaje que aparecerá en Touch ID (En Face ID se usa el del Info.plist)
let reason = "Por favor, identifícate para acceder a tus datos seguros."
// 2. Verificamos si el hardware está disponible y configurado
if context.canEvaluatePolicy(policy, error: &error) {
do {
// 3. Solicitamos la autenticación al sistema de forma asíncrona
let success = try await context.evaluatePolicy(policy, localizedReason: reason)
// Si llegamos aquí sin lanzar error, analizamos el booleano
if success {
self.isUnlocked = true
self.errorMessage = nil
} else {
self.errorMessage = "No se pudo verificar tu identidad."
}
} catch {
// 4. Manejamos los errores que el usuario o el sistema puedan generar
self.errorMessage = evaluateError(error)
}
} else {
// No hay Face ID / Touch ID, o no está configurado
self.errorMessage = "Tu dispositivo no soporta autenticación biométrica o no está configurada."
// Aquí podrías implementar un "Fallback" a contraseña manual
}
}
// Método para bloquear la app nuevamente
func lock() {
self.isUnlocked = false
}
// Helper para traducir los errores de LAError a mensajes amigables
private func evaluateError(_ error: Error) -> String {
guard let laError = error as? LAError else {
return "Ocurrió un error inesperado."
}
switch laError.code {
case .authenticationFailed:
return "Identidad no verificada."
case .userCancel:
return "Autenticación cancelada por el usuario."
case .userFallback:
return "El usuario eligió usar contraseña."
case .biometryNotAvailable:
return "Biometría no disponible en este dispositivo."
case .biometryNotEnrolled:
return "No hay rostros o huellas registradas en el dispositivo."
case .biometryLockout:
return "Demasiados intentos fallidos. Biometría bloqueada."
default:
return "Error de biometría: \(laError.localizedDescription)"
}
}
}
¿Qué hace que este código sea excelente?
Como experto iOS Developer, debes manejar todos los casos extremos. Nuestra función evaluateError traduce los oscuros códigos de error de LAError en mensajes que el usuario final puede entender. Si el usuario falla Face ID 5 veces, el sistema lanza un .biometryLockout. Nuestra app sabrá decirle exactamente qué pasó en lugar de simplemente fallar en silencio.
4. Construyendo la Interfaz: Face ID en SwiftUI
Ahora que tenemos nuestro cerebro (BiometricManager), vamos a conectarlo a una interfaz gráfica utilizando el poder declarativo de SwiftUI.
Queremos una vista que muestre una pantalla de bloqueo con un botón. Cuando el usuario se autentique con éxito, revelaremos el contenido secreto.
Abre tu ContentView.swift y modifícalo de la siguiente manera:
import SwiftUI
struct ContentView: View {
// Instanciamos nuestro manager
@StateObject private var biometricManager = BiometricManager()
var body: some View {
ZStack {
// Fondo dinámico dependiendo del estado
(biometricManager.isUnlocked ? Color.green : Color.blue)
.opacity(0.1)
.ignoresSafeArea()
VStack(spacing: 30) {
if biometricManager.isUnlocked {
// --- VISTA DESBLOQUEADA ---
UnlockedContentView(biometricManager: biometricManager)
} else {
// --- VISTA BLOQUEADA ---
LockedContentView(biometricManager: biometricManager)
}
}
.padding()
.animation(.spring(), value: biometricManager.isUnlocked)
}
}
}
// Sub-vista para el contenido protegido
struct UnlockedContentView: View {
@ObservedObject var biometricManager: BiometricManager
var body: some View {
VStack(spacing: 20) {
Image(systemName: "lock.open.fill")
.font(.system(size: 80))
.foregroundColor(.green)
Text("¡Acceso Concedido!")
.font(.largeTitle)
.bold()
Text("Aquí están tus datos bancarios y secretos de Estado ultra confidenciales.")
.multilineTextAlignment(.center)
.padding()
Spacer()
Button(action: {
biometricManager.lock()
}) {
Text("Cerrar Sesión")
.bold()
.frame(maxWidth: .infinity)
.padding()
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(12)
}
}
}
}
// Sub-vista para la pantalla de inicio de sesión
struct LockedContentView: View {
@ObservedObject var biometricManager: BiometricManager
var body: some View {
VStack(spacing: 20) {
Image(systemName: "faceid")
.font(.system(size: 100))
.foregroundColor(.blue)
Text("App Segura")
.font(.largeTitle)
.bold()
Text("Tus datos están protegidos. Identifícate para continuar.")
.multilineTextAlignment(.center)
.foregroundColor(.secondary)
// Mostrar error si lo hay
if let error = biometricManager.errorMessage {
Text(error)
.foregroundColor(.red)
.font(.footnote)
.multilineTextAlignment(.center)
.padding(.top, 10)
}
Spacer()
Button(action: {
// Ejecutamos la tarea asíncrona usando Task
Task {
await biometricManager.authenticate()
}
}) {
HStack {
Image(systemName: "lock.fill")
Text("Desbloquear con Face ID")
.bold()
}
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(12)
}
}
}
}
Notas sobre SwiftUI:
Al separar la vista en UnlockedContentView y LockedContentView, mantenemos nuestro código principal limpio. Además, fíjate en cómo usamos Task { } dentro del botón de desbloqueo. Dado que nuestro método authenticate() en la programación Swift requiere await, debemos envolverlo en un contexto asíncrono para que SwiftUI pueda llamarlo desde la acción sincrónica del botón.
5. El Poder Multiplataforma: iOS, iPadOS y macOS
Una de las grandes promesas de Apple con SwiftUI es “Aprende una vez, aplica en cualquier lugar”. El código que acabamos de escribir en Xcode es un testimonio brillante de esto.
Aunque el título del artículo menciona explícitamente Face ID en SwiftUI, el framework LocalAuthentication es lo suficientemente inteligente como para abstraer el hardware subyacente.
¿Qué significa esto para ti como iOS Developer?
- En un iPhone 15 Pro o iPad Pro: El código invocará automáticamente los sensores TrueDepth y mostrará el glifo de Face ID.
- En un iPad Air, iPad mini o iPhone SE: El mismo código esperará que el usuario coloque su dedo en el botón de encendido/inicio, activando Touch ID.
- En un MacBook Pro / Air (macOS): Al compilar este mismo código de Swift para Mac, el sistema pedirá automáticamente la huella dactilar del usuario a través del teclado, mostrando el cuadro de diálogo estándar de seguridad de macOS.
Si deseas que tu interfaz de usuario muestre el icono correcto (faceid vs touchid), puedes interrogar al contexto después de llamar a canEvaluatePolicy:
// Fragmento de código para detectar el tipo de biometría
let context = LAContext()
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) {
if context.biometryType == .faceID {
print("El dispositivo usa Face ID")
} else if context.biometryType == .touchID {
print("El dispositivo usa Touch ID")
} else if context.biometryType == .opticID {
print("El dispositivo usa Optic ID (Vision Pro)")
}
}
Consejo Pro: Usa esto para cambiar dinámicamente los iconos e imágenes en tus vistas de SwiftUI para que coincidan con el hardware real del dispositivo de tu usuario.
6. Alternativas (Fallback) a la Biometría
En el mundo real, los usuarios pueden llevar mascarillas (aunque Face ID ha mejorado con eso), tener los dedos mojados, o simplemente fallar los intentos biométricos.
Por defecto, la política .deviceOwnerAuthenticationWithBiometrics mostrará un botón de “Usar Contraseña” si Face ID falla un par de veces. Sin embargo, ese botón de contraseña (cuyo error es .userFallback) delega en ti (el desarrollador) la tarea de mostrar un campo de texto en SwiftUI para pedir una contraseña personalizada de tu app.
Si lo que quieres es que, si Face ID falla, el dispositivo simplemente pida el PIN de desbloqueo del iPhone/Mac (el código de 4 a 6 dígitos), debes cambiar la política:
// En lugar de:
// let policy: LAPolicy = .deviceOwnerAuthenticationWithBiometrics
// Usa esta política:
let policy: LAPolicy = .deviceOwnerAuthentication
Al usar .deviceOwnerAuthentication, el sistema intentará Face ID primero. Si falla, o si no está configurado, mostrará automáticamente el teclado numérico del sistema (el PIN del iPhone) sin que tengas que programar ni una sola línea de código extra. ¡Es una victoria masiva en usabilidad!
7. Advertencia de Seguridad: Face ID vs. Keychain
Como buen iOS Developer, debes conocer las limitaciones arquitectónicas de lo que acabamos de construir.
El framework LocalAuthentication que hemos utilizado te devuelve un booleano (true o false). Básicamente, le estás preguntando a iOS: “¿Es este el usuario?”, e iOS responde “Sí”.
Para el 90% de las aplicaciones (diarios personales, ocultar fotos, proteger un chat), esto es más que suficiente. Sin embargo, si estás desarrollando una aplicación bancaria, una billetera de criptomonedas o manejando datos de salud críticos, este booleano puede ser vulnerado en dispositivos con Jailbreak.
Si necesitas seguridad militar, debes combinar Face ID con el Keychain (Llavero) de Apple. En ese escenario, no solo preguntas si el usuario es válido, sino que configuras una Access Control List (ACL) en el Keychain que le dice al sistema operativo: “No desencriptes este token de acceso a la API a menos que el rostro del usuario pase la validación por hardware”. De esta manera, incluso si un hacker puentea el booleano en memoria, no obtendrá el token porque la clave de encriptación nunca sale del Secure Enclave sin biometría exitosa.
Ese es un tema avanzado para otro tutorial de programación Swift, ¡pero es vital que conozcas la diferencia!
Conclusión
Integrar Face ID en SwiftUI utilizando Xcode es una de las mejoras de calidad de vida más grandes que puedes agregar a tus aplicaciones. Transforma un proceso de inicio de sesión tedioso en una experiencia mágica y sin fricciones de medio segundo.
A lo largo de este tutorial, hemos explorado cómo configurar los permisos de privacidad, construir un administrador asíncrono robusto en Swift, y conectarlo a una hermosa interfaz reactiva capaz de ejecutarse en iOS, iPad y macOS de forma nativa.
Tu labor como iOS Developer es hacer que la tecnología compleja se sienta invisible y sin esfuerzo para el usuario, y la biometría es exactamente eso.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte . Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram










