Introducción
Si llevas algún tiempo desarrollando aplicaciones para iOS con SwiftUI, seguramente te has topado con uno de los muros más clásicos del desarrollo móvil: la gestión del teclado virtual.
Imagina la escena: has diseñado una pantalla de registro perfecta. Los campos de texto están alineados, los colores son armoniosos y la jerarquía visual es impecable. Ejecutas la aplicación en el simulador, tocas el primer campo de texto y… ¡bam! El teclado aparece, cubriendo la mitad inferior de tu pantalla.
Hasta aquí, todo normal. El problema surge cuando el usuario, después de escribir, intenta ver qué hay más abajo. Instintivamente, desliza el dedo por la pantalla para hacer scroll y ver el botón de “Registrarse” que ha quedado oculto. Pero el teclado sigue ahí, inamovible, tapando la interfaz y frustrando al usuario.
Esta experiencia de usuario es deficiente. En el ecosistema de Apple, los usuarios esperan fluidez. Esperan que la interfaz reaccione a sus gestos de forma natural. Si hacen scroll hacia abajo, significa que quieren ver contenido, no seguir escribiendo. Por lo tanto, el teclado debería apartarse.
En los días de UIKit, esto requería una danza compleja de notificaciones del centro de notificaciones (NotificationCenter), cálculos de frames del teclado, ajustes de contentInsets en el UIScrollView y gestores de toques. Era tedioso y propenso a errores.
Afortunadamente, SwiftUI ha madurado enormemente. Si bien las primeras versiones requerían trucos similares o envoltorios de UIKit, las versiones modernas (específicamente a partir de iOS 16 y Xcode 14) nos ofrecen herramientas nativas y extremadamente sencillas para manejar este comportamiento con una sola línea de código.
En este tutorial exhaustivo, exploraremos cómo implementar la funcionalidad de “ocultar teclado al hacer scroll” en SwiftUI. No solo veremos la solución rápida, sino que profundizaremos en las diferentes opciones que nos ofrece Apple, entenderemos por qué funciona y cómo aplicarlo en escenarios del mundo real como listas y formularios complejos.
Requisitos Previos
Para seguir este tutorial y aprovechar las APIs más modernas que vamos a utilizar, necesitas lo siguiente:
- Xcode 14 o superior: Las herramientas que usaremos fueron introducidas con el SDK de iOS 16.
- Target de iOS 16.0 o superior en tu proyecto: Si tu aplicación necesita soportar versiones anteriores (como iOS 14 o 15), mencionaremos brevemente las alternativas al final, pero el foco principal será la forma moderna y recomendada.
- Conocimientos básicos de SwiftUI: Debes sentirte cómodo creando Vistas, usando
StateyBindingbásicos, y entendiendo la estructura de contenedores comoVStackyScrollView.
El Problema: Un Escenario Común
Antes de saltar a la solución, recreemos el problema para entender exactamente qué estamos arreglando.
Vamos a crear una vista simple que simule un formulario largo. Necesitaremos un ScrollView porque el contenido excederá la altura de la pantalla, y varios TextField.
Abre Xcode y crea un nuevo proyecto de SwiftUI. Reemplaza el contenido de ContentView.swift con el siguiente código:
import SwiftUI
struct ContentView: View {
@State private var text1 = ""
@State private var text2 = ""
@State private var text3 = ""
@State private var text4 = ""
@State private var longText = "Aquí hay mucho contenido de relleno para asegurar que la pantalla necesite hacer scroll. Sigue bajando para encontrar más campos de texto."
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 25) {
Text("Formulario Largo")
.font(.largeTitle)
.fontWeight(.bold)
.padding(.top)
Text(longText)
.padding()
TextField("Campo superior 1", text: $text1)
.textFieldStyle(.roundedBorder)
.padding(.horizontal)
TextField("Campo superior 2", text: $text2)
.textFieldStyle(.roundedBorder)
.padding(.horizontal)
// Espacio de relleno para forzar el scroll
Color.gray.opacity(0.2)
.frame(height: 300)
.overlay(Text("Contenido intermedio..."))
TextField("Campo inferior 3 (Problemático)", text: $text3)
.textFieldStyle(.roundedBorder)
.padding(.horizontal)
TextField("Campo inferior 4 (Muy problemático)", text: $text4)
.textFieldStyle(.roundedBorder)
.padding(.horizontal)
Button(action: {
print("Formulario enviado")
}) {
Text("Enviar Formulario")
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.padding()
}
}
.navigationTitle("Demo Teclado")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}Análisis del Problema
Ejecuta este código en un simulador (preferiblemente uno con pantalla pequeña como un iPhone SE o iPhone 14/15 estándar, no un Pro Max, para acentuar el problema).
- Toca en el “Campo inferior 4”.
- El teclado sube y, gracias a la gestión automática de SwiftUI, la vista intentará desplazarse para que el campo de texto sea visible justo encima del teclado.
- Ahora, intenta hacer scroll hacia arriba para ver el título “Formulario Largo” o el primer campo de texto.
- Observación: El contenido se desplaza detrás del teclado, pero el teclado permanece obstinadamente abierto. Tienes que buscar explícitamente un botón de “Done” o “Return” en el teclado (si lo configuraste) para cerrarlo.
Esto es lo que queremos solucionar. El gesto de scroll del usuario es una intención clara de “quiero navegar, ya no quiero escribir”.
La Solución Moderna: .scrollDismissesKeyboard(_:)
Apple escuchó nuestras plegarias en la WWDC 22 con la introducción de iOS 16. Introdujeron un nuevo modificador de vista específicamente diseñado para controlar este comportamiento en contenedores desplazables (ScrollView, List, Form).
El modificador es:
.scrollDismissesKeyboard(_ mode: ScrollDismissesKeyboardMode)Este modificador se aplica directamente al contenedor que hace scroll.
Los Modos de Desplazamiento
El parámetro mode es un enum ScrollDismissesKeyboardMode que nos da un control granular sobre cómo y cuándo debe desaparecer el teclado. Veamos las opciones disponibles:
.automatic(Por defecto) Este es el comportamiento predeterminado del sistema. SwiftUI intenta adivinar qué es lo mejor basándose en el contexto.- En una
List, el comportamiento por defecto suele ser ocultar el teclado interactivamente al arrastrar. - En un
ScrollViewgenérico, a veces el comportamiento por defecto no hace nada (como vimos en nuestro ejemplo anterior). Es por eso que a menudo necesitamos ser explícitos.
- En una
.interactively(La opción recomendada para UX) Esta es la joya de la corona de la experiencia de usuario en iOS. Cuando usas este modo, el teclado se descarta siguiendo el dedo del usuario mientras hace el gesto de scroll. Si el usuario comienza a hacer scroll y luego se arrepiente y vuelve a la posición original sin levantar el dedo, el teclado vuelve a subir. Es el comportamiento que ves en la aplicación de Mensajes (iMessage) cuando estás escribiendo un mensaje y decides hacer scroll hacia arriba para leer el historial. Se siente natural, físico y muy pulido..immediatelyComo su nombre indica, el teclado desaparece instantáneamente en el momento en que se detecta cualquier movimiento de scroll. No hay animación de seguimiento con el dedo. Es un cierre brusco. Es útil si quieres una respuesta muy rápida, pero generalmente se siente menos “nativo” que el modo interactivo..neverFuerza al teclado a permanecer visible incluso si el usuario hace scroll. Hay pocos casos de uso para esto, quizás en una app de terminal o consola donde siempre necesitas el teclado activo, pero para formularios estándar, esto suele ser una mala UX.
Aplicando la Solución a Nuestro Ejemplo
Vamos a arreglar nuestro ContentView.swift utilizando el modo que ofrece la mejor experiencia: .interactively.
Es crucial recordar dónde aplicar este modificador. No se aplica a los TextField, ni al VStack. Se aplica al contenedor que genera el desplazamiento, en este caso, el ScrollView.
Modifica el código de tu ContentView.swift de la siguiente manera:
import SwiftUI
struct ContentView: View {
// ... (todas tus variables de estado @State siguen igual) ...
@State private var text1 = ""
@State private var text2 = ""
@State private var text3 = ""
@State private var text4 = ""
@State private var longText = "Aquí hay mucho contenido de relleno para asegurar que la pantalla necesite hacer scroll. Sigue bajando para encontrar más campos de texto."
var body: some View {
NavigationView {
ScrollView { // <-- Identificamos el contenedor de scroll
VStack(spacing: 25) {
// ... (Todo el contenido de tu VStack sigue exactamente igual) ...
Text("Formulario Largo")
.font(.largeTitle)
// ... etc, etc ...
TextField("Campo inferior 4 (Muy problemático)", text: $text4)
.textFieldStyle(.roundedBorder)
.padding(.horizontal)
Button(action: {
print("Formulario enviado")
}) {
Text("Enviar Formulario")
// ... estilos del botón
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(10)
}
.padding()
}
}
.navigationTitle("Demo Teclado")
// APLICAMOS LA SOLUCIÓN AQUÍ:
.scrollDismissesKeyboard(.interactively)
}
}
}Nota: Asegúrate de que tu simulador o dispositivo ejecute iOS 16 o superior para que esto funcione.
Probando la Solución
Vuelve a ejecutar la aplicación.
- Toca de nuevo el “Campo inferior 4”. El teclado aparece.
- Ahora, haz scroll lentamente hacia arriba o hacia abajo.
- Observa la magia: El teclado comienza a bajar siguiendo exactamente la velocidad y posición de tu dedo. Si lo sueltas a mitad de camino, termina de bajar. Si inviertes el gesto antes de soltar, vuelve a subir.
¡Problema resuelto con una sola línea de código!
Profundizando: Listas y Formularios
El ejemplo anterior usaba un ScrollView genérico. ¿Qué pasa con las vistas estructuradas de SwiftUI como List y Form?
Estas vistas ya vienen con comportamientos de teclado más inteligentes “de fábrica”. A menudo, un List ya tiene el comportamiento .interactively activado por defecto en iOS moderno.
Sin embargo, siempre es una buena práctica ser explícito en tu código para garantizar que el comportamiento sea consistente, sin importar los cambios futuros en los valores por defecto del sistema.
Si cambias tu ScrollView por un Form en el ejemplo anterior, el modificador sigue funcionando exactamente igual:
NavigationView {
Form {
// ... tus campos de texto ...
}
.navigationTitle("Formulario Demo")
.scrollDismissesKeyboard(.interactively) // Funciona perfectamente en Forms
}Esto es excelente porque unifica la forma en que manejamos el teclado en todos los tipos de contenedores desplazables.
Un Paso Más Allá: Ocultar el teclado al tocar fuera (Tap Outside)
Aunque este tutorial se centra en ocultar el teclado al hacer scroll, una gestión completa del teclado a menudo también requiere que el teclado se oculte cuando el usuario toca en cualquier parte de la pantalla que no sea un campo de texto (un “tap outside”).
El modificador .scrollDismissesKeyboard no maneja los toques, solo el desplazamiento.
Si el usuario toca una zona vacía del ScrollView sin arrastrar, el teclado seguirá ahí. Para una experiencia de usuario de 5 estrellas, deberíamos combinar ambas técnicas.
En SwiftUI moderno, la forma más robusta de manejar el “tap outside” es usando un overlay con un TapGesture que fuerce el fin de la edición en toda la jerarquía de vistas.
Podemos añadir una pequeña extensión a View para facilitar esto:
extension View {
func endTextEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}(Nota: Aunque UIApplication.shared parece “vieja escuela” de UIKit, sigue siendo la forma más confiable de cerrar el teclado globalmente en SwiftUI hasta la fecha).
Ahora, combinemos esto en nuestra ContentView:
struct ContentView: View {
// ... estados ...
var body: some View {
NavigationView {
ScrollView {
VStack(spacing: 25) {
// ... contenido del formulario ...
}
}
.navigationTitle("Demo Teclado Pro")
// Solución 1: Ocultar al hacer Scroll
.scrollDismissesKeyboard(.interactively)
// Solución 2: Ocultar al tocar fuera
.onTapGesture {
self.endTextEditing()
}
}
}
}Al añadir .onTapGesture al contenedor principal (o al ScrollView), cualquier toque que no sea interceptado por un botón o un campo de texto activará la función endTextEditing(), cerrando el teclado.
Combinando .scrollDismissesKeyboard(.interactively) y el .onTapGesture, has cubierto el 99% de las frustraciones que los usuarios tienen con los teclados en iOS.
¿Qué pasa con las versiones antiguas de iOS (pre-iOS 16)?
Si tu proyecto tiene como objetivo mínimo iOS 14 o iOS 15, el modificador .scrollDismissesKeyboard no estará disponible y el compilador te dará un error.
En estos casos, la solución es más “manual”. Implica detectar el gesto de arrastre en el ScrollView y cerrar el teclado programáticamente.
Aquí tienes un ejemplo de cómo se hacía (y se sigue haciendo para soporte legado):
// Solución para iOS 14 / 15
ScrollView {
VStack {
// Contenido
}
}
.gesture(
DragGesture().onChanged { _ in
// Si se detecta cualquier arrastre, cierra el teclado
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
)Desventajas de este método antiguo:
- No es “interactivo”. El teclado no sigue el dedo; simplemente se cierra abruptamente en cuanto empieza el arrastre (similar al modo
.immediately). - El
DragGesturepuede a veces entrar en conflicto con otros gestos dentro del ScrollView si tienes elementos interactivos complejos (como un mapa o un carrusel horizontal dentro del scroll vertical).
Por estas razones, se recomienda encarecidamente utilizar la API moderna .scrollDismissesKeyboard siempre que la compatibilidad de tu proyecto lo permita. El resultado es mucho más profesional y requiere menos código de mantenimiento.
Conclusión
La gestión del teclado ha pasado de ser un dolor de cabeza constante en el desarrollo de iOS a ser una tarea trivial gracias a las mejoras en SwiftUI.
El modificador .scrollDismissesKeyboard(.interactively) es una herramienta esencial que debería estar en el kit de herramientas de cualquier desarrollador de SwiftUI. Proporciona esa sensación de “calidad Apple” con un esfuerzo mínimo, asegurando que tus usuarios nunca se sientan atrapados por el teclado virtual mientras navegan por tus formularios o listas.
Recuerda siempre pensar en la intención del usuario: si están haciendo scroll, es porque quieren ver contenido, no escribir. Facilítales la vida apartando el teclado de su camino.
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










