iPad: clases de tamaño, multitarea y el precio de estirar
El iPad no es un iPhone grande, y una app que lo trata así se delata en segundos: líneas de texto interminables, controles varados en un borde, modales que ocupan una pantalla entera para pedir un nombre. Esta lección sustituye el razonamiento por dispositivo por el razonamiento por clase de tamaño, examina qué significa que la ventana ya no te pertenezca y establece los contenedores adaptativos como forma por defecto de expresar jerarquía.
Casi nadie llama al iPhone un iPad pequeño, y esa asimetría del lenguaje delata algo real: la diferencia entre ambos no es la diagonal en pulgadas sino lo que la persona espera hacer con el aparato. En el teléfono se atiende una cosa cada vez, con el pulgar, de pie y con prisa. En la tableta se compara, se arrastra, se escribe con teclado físico y se conviven dos apps a la vez porque el trabajo consiste precisamente en cruzarlas. Una app que ignora esa diferencia no falla de forma espectacular: arranca, se ve, responde. Y aun así produce una incomodidad difusa que rara vez se verbaliza como defecto y que se traduce puntualmente en abandono.
- Razonar con
size classen lugar de con nombres de dispositivo. - Diseñar para un ancho variable que tu app no controla ni puede predecir.
- Reconocer los síntomas del estiramiento y localizar su causa estructural.
- Adoptar los contenedores adaptativos como expresión por defecto de la jerarquía.
La clase de tamaño no describe un aparato
La plataforma no te ofrece una pregunta sobre el dispositivo, y no es un olvido. Te ofrece dos valores en el entorno —la clase de tamaño horizontal y la vertical— que solo pueden ser compact o regular, y que describen el espacio realmente disponible para tu vista en este instante. Un iPhone en apaisado tiene clase horizontal regular en los modelos grandes; un iPad con tu app ocupando un tercio de la pantalla tiene clase horizontal compact. La consecuencia es incómoda pero liberadora: regular no significa iPad y compact no significa iPhone. Significan, respectivamente, hay sitio y no hay sitio.
@Environment(\.horizontalSizeClass) private var ancho
var body: some View {
if ancho == .regular {
PanelDoble() // hay sitio: dos cosas a la vez
} else {
PanelApilado() // no hay sitio: una cada vez
}
}
Este fragmento es correcto y, aun así, conviene desconfiar de él. Ramificar por clase de tamaño es la herramienta de último recurso, no la primera: introduce dos ramas que hay que probar, mantener y sincronizar, y sobre todo codifica una decisión que el sistema podría tomar mejor. La regla práctica que separa a los equipos que entienden la adaptación de los que la sufren es simple: ramifica cuando la estructura de la información cambia —una lista y su detalle conviven o se turnan—, nunca cuando lo único que cambia es una medida. Para las medidas hay contenedores.
Comprobar el modelo del dispositivo condena tu app a envejecer con cada tamaño nuevo. Consultar el ancho de la pantalla física ignora que tu ventana puede ser un tercio de ella. Y consultar el idioma de interfaz para deducir el ancho del texto es un error todavía más sutil, porque el alemán y el árabe cambian la medida sin cambiar el aparato. Los tres comparten la misma raíz: preguntan por el mundo en lugar de por el espacio concedido.
La ventana dejó de pertenecerte
En el teléfono el ancho es una constante que cambia solo al rotar. En el iPad no lo es. Split View reparte la pantalla en dos apps con un separador que la persona arrastra a voluntad; Slide Over deposita una app estrecha flotando encima; Stage Manager convierte las apps en ventanas redimensionables con un ratón. En los tres casos el ancho de tu vista es una variable continua y ajena, controlada por alguien que no eres tú y que puede cambiarla mientras el usuario escribe.
flowchart TB a[La persona arrastra el separador] --> b[Cambia el ancho de la ventana] b --> c[La clase de tamano puede pasar de regular a compact] c --> d[NavigationSplitView colapsa en pila] b --> e[El layout se recalcula de forma continua] d --> f[El estado de navegacion debe sobrevivir] e --> f
De ahí se sigue una exigencia que muchas apps incumplen sin saberlo: el estado tiene que sobrevivir a la transición. Si tu navegación guarda índices posicionales, referencias a objetos volátiles o un booleano que significa el panel está abierto, el colapso a pila te dejará en un lugar que nadie pidió. Si guarda identificadores y una selección declarada, el sistema traduce esa selección en pantallas apiladas y de vuelta sin que tú escribas nada. Es la misma lección de siempre con ropa nueva: lo que es un valor se puede transformar, y lo que es un efecto secundario se pierde.
struct Editor: View {
@State private var seleccion: Nota.ID? // identificador, no objeto
@State private var ruta: [RutaDetalle] = [] // historial representable
var body: some View {
NavigationSplitView {
List(notas, selection: $seleccion) { Text($0.titulo) }
} detail: {
NavigationStack(path: $ruta) { Detalle(id: seleccion) }
}
}
}
Hay además una consecuencia de rendimiento que se nota más en tableta que en teléfono. Durante el arrastre del separador el sistema recalcula tu jerarquía muchas veces por segundo, de modo que cualquier cálculo caro colocado en el cuerpo de una vista —una ordenación, un formateo de fecha, una decodificación— deja de ser una ineficiencia teórica y se convierte en un tirón visible que la persona atribuye, con razón, a tu app.
Los síntomas del estiramiento
Una app de teléfono ampliada a tableta no produce un error, produce un olor. Conviene aprender a reconocerlo por sus manifestaciones, porque casi todas comparten una única causa: el diseño se pensó para un ancho fijo y al crecer el ancho nadie decidió qué debía crecer con él.
La línea kilométrica
Un párrafo que ocupa mil puntos de ancho obliga al ojo a saltar de renglón sin referencia. La medida legible ronda los sesenta y cinco caracteres, y el espacio sobrante debe ir a los márgenes o a una segunda columna, nunca al texto.
El control exiliado
Un botón anclado a la esquina superior derecha estaba a un centímetro del pulgar en el teléfono y queda a veinte en la tableta. La zona cómoda cambia de forma cuando el aparato se sujeta con dos manos.
El modal desmesurado
Una hoja a pantalla completa para pedir un nombre y confirmar tenía sentido cuando la pantalla era del tamaño de la mano. En tableta interrumpe todo el contexto para una decisión trivial.
El carril único
Una lista de un elemento por fila desperdicia dos tercios del espacio y obliga a desplazar donde cabría comparar. La rejilla adaptativa existe justo para esto.
Ninguno de estos síntomas se cura con una condición sobre el dispositivo. Se curan expresando la intención: este texto tiene una medida máxima legible, este contenido se reparte en tantas columnas como quepan, esta hoja pide el tamaño que necesita. Cuando la intención está declarada, el ancho deja de ser un problema y pasa a ser un dato de entrada.
Text(cuerpo)
.frame(maxWidth: 680) // medida legible, no ancho de pantalla
LazyVGrid(columns: [GridItem(.adaptive(minimum: 240), spacing: 16)]) {
ForEach(tarjetas) { Tarjeta($0) } // tantas columnas como quepan
}
.sheet(isPresented: $editando) {
FormularioNombre()
.presentationDetents([.medium]) // pide lo que necesita
}
ViewThatFits prueba sus alternativas en orden y se queda con la primera que cabe en el espacio ofrecido. Es el contenedor que convierte una ramificación explícita en una negociación: en lugar de preguntar si estás en regular, colocas la versión horizontal primero y la vertical después, y el sistema decide con información que tú no tienes. Funciona igual en un iPad, en una ventana estrecha de Mac y en un tamaño que todavía no existe.
Lo que la tableta añade y el teléfono no pedía
Adaptarse al ancho evita el ridículo, pero no basta para que la app parezca nacida en el iPad. Hay un conjunto de capacidades que en el teléfono son adorno y aquí son expectativa. El teclado físico convierte los atajos en el camino principal de quien trabaja rápido, y publicarlos con keyboardShortcut los inscribe además en el menú que aparece al mantener pulsada la tecla de comando. El puntero del trackpad exige realimentación al pasar por encima, que hoverEffect proporciona con el efecto correcto para cada forma. Y arrastrar y soltar deja de ser una función avanzada para volverse la manera obvia de mover algo entre dos apps abiertas a la vez.
Button("Nueva nota", action: crear)
.keyboardShortcut("n", modifiers: .command) // el teclado es camino principal
TarjetaNota(nota)
.hoverEffect(.highlight) // el puntero necesita respuesta
.draggable(nota) // arrastrar entre apps abiertas
Conviene entender por qué estas tres cosas aparecen juntas y no por casualidad. Las tres son consecuencias de una misma condición: en la tableta la app no está sola, ni en la pantalla ni en la atención de quien la usa. El teclado importa porque hay una sesión larga; el puntero importa porque hay precisión; el arrastre importa porque hay un destino fuera de tu proceso. Diseñar para iPad es, en el fondo, aceptar que dejas de ser el centro de la experiencia y pasas a ser una pieza cooperante dentro de ella.
Cuando tu app comparte pantalla puede perder el foco sin pasar a segundo plano, y eso rompe la suposición de que solo se trabaja cuando se es visible. Una escena en Slide Over sigue viva y visible mientras la persona escribe en otra app. Si pausabas temporizadores, descargas o reproducción al perder el foco, revisa esa decisión: en tableta la app inactiva puede seguir siendo la que importa.
La observación que ordena todo este nivel es que una app de iPhone ejecutada en un iPad no está sin adaptar: está adaptada según una regla que nadie eligió conscientemente. Alguien decidió, por omisión, que el espacio sobrante debía ir al texto en vez de a los márgenes, que la lista mantendría un carril en vez de dos, que la hoja seguiría cubriendo la pantalla. Esas son decisiones de diseño con las mismas consecuencias que si se hubieran tomado en una reunión, solo que sin nadie que las defienda ni las revise. El valor de las clases de tamaño y de los contenedores adaptativos no es técnico, entonces, sino epistémico: hacen visible el conjunto de decisiones que hasta ese momento estaban implícitas en el ancho fijo del primer dispositivo para el que se escribió el código. Y una vez visibles, se pueden discutir, medir y equivocar a propósito, que es la única forma de aprender. Fíjate en que el patrón se repetirá idéntico en las cuatro lecciones que siguen: el Mac hará explícitas las decisiones sobre ventanas y menús que el teléfono resolvía por ti, el reloj hará explícita la decisión sobre cuánto tiempo dura una interacción, y visionOS hará explícita la decisión sobre dónde vive la interfaz. Cambiar de plataforma no es portar código; es descubrir cuántas constantes de tu diseño eran en realidad variables que nadie había nombrado.
Las clases de tamaño describen espacio disponible, no aparatos: regular no es iPad. En multitarea el ancho es una variable continua y ajena, así que el estado de navegación debe ser representable para sobrevivir al colapso y a la expansión. Ramifica solo cuando cambie la estructura de la información; para todo lo demás usa contenedores que negocien —ViewThatFits, rejillas adaptativas, medidas máximas de texto—. Y añade lo que la tableta espera y el teléfono no pedía: atajos de teclado, respuesta al puntero y arrastre entre apps.
- Ejecuta tu app en un iPad y arrastra el separador de Split View de un tercio a dos tercios anotando cada punto en el que algo se rompe, se estira o desaparece.
- Sustituye cualquier ramificación por dispositivo que encuentres por una ramificación por clase de tamaño, y después intenta eliminar esa ramificación con
ViewThatFitso una rejilla adaptativa. - Fija una medida máxima legible a todos los bloques de texto largo y comprueba que el espacio sobrante va a los márgenes.
- Verifica que el estado de navegación sobrevive al colapso: selecciona un detalle en dos columnas, reduce la ventana hasta que colapse en pila y vuelve a expandirla.
- Añade atajos de teclado a las tres acciones más frecuentes y comprueba que aparecen al mantener pulsada la tecla de comando.