En el ecosistema de desarrollo móvil, la retención de usuarios es tan importante como la adquisición. Para mantener a los usuarios comprometidos, las notificaciones son la herramienta más potente a nuestra disposición. Sin embargo, existe una confusión habitual entre los tres tipos principales de mensajes que podemos mostrar: Notificaciones Push (Remotas), Notificaciones Locales y Notificaciones In-App.
Aunque para el usuario final una “alerta” puede parecer siempre lo mismo, a nivel de arquitectura, código e infraestructura, son bestias completamente diferentes.
Este artículo desglosa cada una, explica sus diferencias técnicas y te enseña a implementarlas utilizando Swift y SwiftUI.
Parte 1: El Panorama General
Antes de escribir una sola línea de código, debemos entender la “fuente de la verdad” de cada notificación.
- Notificaciones Locales: El disparador (trigger) es el propio dispositivo (tiempo, fecha o ubicación). No requieren internet.
- Notificaciones Push (Remotas): El disparador es un servidor externo. Requieren internet y pasan por los servidores de Apple (APNs).
- Notificaciones In-App: El disparador es una acción del usuario dentro de la app o un estado de la interfaz. Son simplemente Vistas de SwiftUI personalizadas.
Parte 2: Notificaciones Locales (Local Notifications)
Las notificaciones locales son programadas por la propia aplicación y entregadas por el dispositivo en el futuro. Son ideales para recordatorios, alarmas o listas de tareas (“To-Do”).+1
Características Clave
- Independencia: No necesitan un backend.
- Fiabilidad: Funcionan en modo avión (siempre que el dispositivo esté encendido).
- Framework:
UserNotifications.
Implementación en SwiftUI
Para trabajar con notificaciones locales y remotas de manera limpia en SwiftUI, lo ideal es crear una clase gestora (NotificationManager).
Paso 1: Configurar el Gestor y Pedir Permisos
El sistema operativo iOS requiere que el usuario autorice explícitamente las notificaciones.
import SwiftUI
import UserNotifications
class NotificationManager: NSObject, ObservableObject {
static let shared = NotificationManager()
override init() {
super.init()
}
// 1. Solicitud de Permisos
func requestAuthorization() {
let options: UNAuthorizationOptions = [.alert, .sound, .badge]
UNUserNotificationCenter.current().requestAuthorization(options: options) { granted, error in
if let error = error {
print("Error al solicitar permisos: \(error.localizedDescription)")
} else if granted {
print("Permiso concedido")
} else {
print("Permiso denegado")
}
}
}
}Paso 2: Programar la Notificación
Una notificación local tiene tres partes:
- Contenido: ¿Qué dice?
- Disparador (Trigger): ¿Cuándo se muestra?
- Petición (Request): La combinación de los dos anteriores.
// Dentro de NotificationManager
func scheduleLocalNotification(title: String, body: String, timeInterval: TimeInterval) {
// A. El Contenido
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
// B. El Disparador
// Se dispara después de X segundos. 'repeats: false' significa que solo ocurre una vez.
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false)
// C. La Petición
// El identificador debe ser único si quieres gestionar esta notificación específica después
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
// D. Añadir al Centro de Notificaciones
UNUserNotificationCenter.current().add(request) { error in
if let error = error {
print("Error al programar: \(error.localizedDescription)")
} else {
print("Notificación programada para dentro de \(timeInterval) segundos")
}
}
}Paso 3: Integración en la Vista SwiftUI
struct LocalNotificationView: View {
@StateObject private var notificationManager = NotificationManager.shared
var body: some View {
VStack(spacing: 20) {
Text("Notificaciones Locales")
.font(.title)
Button("Pedir Permisos") {
notificationManager.requestAuthorization()
}
Button("Programar para 5 segundos") {
notificationManager.scheduleLocalNotification(
title: "Hola SwiftUI",
body: "Esta es una notificación local",
timeInterval: 5
)
}
}
}
}Nota importante: Si la app está en primer plano (foreground), por defecto iOS no muestra el banner de la notificación. Para forzarlo, debes implementar el delegado
userNotificationCenter(_:willPresent:withCompletionHandler:)en tuNotificationManager.
Parte 3: Notificaciones Push (Remote Notifications)
Aquí es donde la arquitectura se complica. Una notificación Push es un mensaje enviado desde tu servidor a Apple Push Notification service (APNs), y luego de APNs al dispositivo del usuario.
Requisitos Previos
Para probar esto, necesitas:
- Una cuenta de Apple Developer (pagada).
- Un dispositivo físico (aunque Xcode 14+ permite simulaciones limitadas, lo real es mejor).
- Certificados Push o una clave de autenticación
.p8configurada en el portal de desarrolladores.
El Flujo de Trabajo
- La App solicita registrarse para notificaciones remotas.
- iOS solicita un Device Token a APNs.
- La App recibe el token y lo envía a tu servidor backend.
- Tu servidor usa ese token para enviar mensajes a ese usuario específico.
Implementación en SwiftUI
SwiftUI no tiene un “AppDelegate” por defecto en el ciclo de vida moderno (WindowGroup), pero lo necesitamos para registrar las Push. Debemos usar UIApplicationDelegateAdaptor.
Paso 1: Crear el AppDelegate Adaptado
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// Configurar delegado para manejar notificaciones en primer plano
UNUserNotificationCenter.current().delegate = self
// Solicitar autorización (igual que en locales)
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in }
// Registrarse para remotas
application.registerForRemoteNotifications()
return true
}
// ÉXITO al obtener el Token
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// Convertir el token a String hexadecimal
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
// TODO: Enviar este token a tu servidor backend (API)
}
// ERROR al obtener el Token
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Fallo al registrar notificaciones remotas: \(error)")
}
}Paso 2: Conectar al Punto de Entrada de la App
En tu archivo principal (TuApp.swift):
@main
struct TuApp: App {
// Inyectamos el AppDelegate
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}Probando Push sin Backend
Puedes probar una notificación push arrastrando un archivo .apns al simulador de iOS. Crea un archivo llamado payload.apns:
{
"Simulator Target Bundle": "com.tuempresa.tuapp",
"aps": {
"alert": {
"title": "Prueba Push",
"body": "Esto viene del servidor (simulado)"
},
"sound": "default",
"badge": 1
}
}Arrastra este archivo sobre el simulador y verás la magia.
Parte 4: Notificaciones In-App
Las “In-App Notifications” son las grandes incomprendidas. A menudo, los desarrolladores intentan usar notificaciones locales para mostrar mensajes de éxito (ej. “Perfil guardado”). Esto es un error de UX.
Si el usuario ya está mirando la pantalla, no necesitas el sistema de notificaciones del sistema operativo (que invade el centro de notificaciones). Necesitas una Vista Superpuesta.
Características Clave
- Control Total: Tú decides el diseño, color, animación y duración.
- Contexto: Solo existen mientras la app está abierta.
- Sin Permisos: No requieren permiso del usuario ni certificados de Apple.
- Tecnología: Vistas estándar de SwiftUI (
ZStack,overlay,geometryEffect).
Implementación: Un “Toast” o “Snackbar” Personalizado
Vamos a crear un sistema reutilizable para mostrar notificaciones dentro de la app.
Paso 1: El Modelo y el ViewModel
struct InAppNotification: Equatable {
var id = UUID()
var title: String
var type: NotificationType
enum NotificationType {
case success
case error
case info
var color: Color {
switch self {
case .success: return Color.green
case .error: return Color.red
case .info: return Color.blue
}
}
var icon: String {
switch self {
case .success: return "checkmark.circle.fill"
case .error: return "xmark.circle.fill"
case .info: return "info.circle.fill"
}
}
}
}
class InAppNotificationManager: ObservableObject {
@Published var currentNotification: InAppNotification?
func show(title: String, type: InAppNotification.NotificationType) {
withAnimation(.spring()) {
currentNotification = InAppNotification(title: title, type: type)
}
// Ocultar automáticamente después de 3 segundos
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
withAnimation {
// Solo eliminamos si es la misma notificación (para evitar condiciones de carrera)
if self.currentNotification?.title == title {
self.currentNotification = nil
}
}
}
}
}Paso 2: La Vista de la Notificación
struct InAppNotificationView: View {
let notification: InAppNotification
var body: some View {
HStack {
Image(systemName: notification.type.icon)
.foregroundColor(.white)
Text(notification.title)
.foregroundColor(.white)
.font(.subheadline)
.fontWeight(.medium)
Spacer()
}
.padding()
.background(notification.type.color)
.cornerRadius(10)
.shadow(radius: 5)
.padding(.horizontal)
}
}Paso 3: El Modificador (Modifier) para Usarlo Fácilmente
Para no ensuciar nuestras vistas con ZStacks por todas partes, creamos un ViewModifier.
struct InAppNotificationModifier: ViewModifier {
@ObservedObject var manager: InAppNotificationManager
func body(content: Content) -> some View {
ZStack(alignment: .top) {
content
if let notification = manager.currentNotification {
InAppNotificationView(notification: notification)
.transition(.move(edge: .top).combined(with: .opacity))
.zIndex(1) // Asegura que esté encima
.padding(.top, 50) // Espacio para la Dynamic Island o Notch
}
}
}
}
extension View {
func withInAppNotifications(manager: InAppNotificationManager) -> some View {
self.modifier(InAppNotificationModifier(manager: manager))
}
}Paso 4: Uso Final
struct ContentView: View {
@StateObject private var inAppManager = InAppNotificationManager()
var body: some View {
VStack(spacing: 20) {
Button("Mostrar Éxito") {
inAppManager.show(title: "Datos guardados correctamente", type: .success)
}
Button("Mostrar Error") {
inAppManager.show(title: "Error de conexión", type: .error)
}
}
// Inyectamos el sistema de notificaciones en la raíz
.withInAppNotifications(manager: inAppManager)
}
}Parte 5: Tabla Comparativa y Resumen
Para consolidar el conocimiento, aquí tienes una comparación directa de las tres tecnologías.
| Característica | Notificación Local | Notificación Push | Notificación In-App |
| Origen | Dispositivo (Código App) | Servidor Externo (Backend) | Dispositivo (Estado App) |
| Infraestructura | UserNotifications Framework | APNs + Backend + UserNotifications | SwiftUI (Vistas) |
| Requiere Internet | No | Sí | No |
| Permisos de Usuario | Sí (Diálogo de sistema) | Sí (Diálogo de sistema) | No |
| Visibilidad | Fuera de la app (Lock screen/Banner) | Fuera de la app (Lock screen/Banner) | Solo dentro de la app |
| Costo | Gratis | Requiere Apple Developer Program (99$/año) | Gratis |
| Caso de Uso Principal | Alarmas, Recordatorios basados en tiempo/geo | Noticias, Chat, Ofertas, Re-engagement | Feedback visual, Errores, Confirmaciones |
¿Cuándo usar cuál?
- Usa Push: Cuando ocurre algo importante fuera del teléfono del usuario que deben saber inmediatamente (ej: “Tu pedido ha sido enviado”, “Nuevo mensaje de Juan”).
- Usa Local: Cuando la app debe recordar algo al usuario basado en datos que la app ya tiene, sin necesidad de servidor (ej: “Llevas 3 días sin meditar”, “Tu tarea vence en 10 minutos”).
- Usa In-App: Cuando el usuario está interactuando activamente con tu app y necesitas darle feedback inmediato (ej: “Contraseña actualizada”, “Error al cargar datos”). Nunca uses notificaciones Push o Locales para esto, rompe la inmersión.
Conclusión
Dominar las notificaciones en iOS significa entender el contexto del usuario.
- El código para Push es complejo por la infraestructura (Certificados, APNs).
- El código para Local es verboso por la configuración de Triggers.
- El código para In-App es creativo y depende puramente de tus habilidades de diseño en SwiftUI.
Como desarrollador Swift, tu objetivo no es solo “hacer que suene el teléfono”, sino elegir el canal adecuado para que el usuario agradezca la información en lugar de desinstalar la app por molesta.
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









