marbuilds / recursosCuaderno de comportamiento / edición 1.0 / septiembre 2026

Tu app funciona.
Pero todavía
parece una web.

17 detalles de comportamiento que separan una app nativa de una web con un icono.

La mayoría no son diseño. Son comportamiento, y casi todos se comprueban con el móvil en la mano.

Para quien construye apps para iOS, con o sin React Native. Con la fuente de Apple en cada punto.

17 detalles

01Navegación

La navegación es la del sistema

las pilas, las tabs y las transiciones son las de iOS, no una recreación
Pendiente

Cuándo aplicacualquier app con más de una pantalla

Qué estás comprobandoLas pilas de pantallas, las barras y las tabs usan los componentes de navegación de la plataforma en lugar de vistas propias animadas a mano. Apple recomienda los patrones estándar porque hacen que la app resulte familiar desde el primer uso, y porque traen gratis comportamientos que nadie recuerda reimplementar.

La prueba, con el móvil en la mano

Abre una pantalla de detalle y vuelve. Haz lo mismo en Ajustes o en Mail del propio iPhone, una detrás de otra, sin soltar el móvil. Compara la curva, la duración y por dónde entra la pantalla.

Pasa si

La transición es indistinguible de la de una app del sistema, el título de la barra se comporta igual y el botón de volver aparece solo.

En React Native o Expo

expo-router y @react-navigation/native-stack usan el controlador de navegación nativo por debajo. El stack en JavaScript (@react-navigation/stack) dibuja la transición con Animated: se parece, pero no es el mismo componente. Si usas expo-router ya estás en native-stack salvo que lo hayas cambiado a propósito.

Detalle técnico

Las pantallas se declaran, no se animan:

// app/_layout.tsx
import { Stack } from 'expo-router';

export default function Layout() {
  return (
    <Stack>
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="detalle/[id]" options={{ title: 'Detalle' }} />
    </Stack>
  );
}

Si en tu proyecto aparece una animación de pantalla escrita a mano, ese es el sitio por el que empezar a mirar.

02Navegación

Volver atrás arrastrando desde el borde

el gesto estándar funciona en todas las pantallas y se puede cancelar a mitad
Pendiente

Cuándo aplicapilas de navegación y vistas modales

Qué estás comprobandoArrastrar desde el borde izquierdo vuelve a la pantalla anterior y arrastrar una hoja hacia abajo la cierra. Son gestos estándar de iOS: la gente los hace sin pensar y su ausencia se nota antes que cualquier detalle visual.

La prueba, con el móvil en la mano

Arrastra desde el borde izquierdo en cada pantalla de la pila. Después hazlo a medias: empieza el gesto, para a la mitad y suelta sin llegar al final.

Pasa si

El gesto funciona en toda la pila y, si lo sueltas a mitad, la pantalla vuelve a su sitio acompañando al dedo en lugar de saltar.

En React Native o Expo

En native-stack el gesto es gestureEnabled, activo por defecto en iOS. Si sustituyes la pila por vistas propias desaparece sin ningún aviso: no hay error, simplemente deja de funcionar y nadie lo prueba.

Detalle técnico

Se apaga donde molesta, no globalmente:

<Stack.Screen
  name="mapa"
  options={{ gestureEnabled: false }}
/>
03Navegación

Sheets del sistema, con su dismiss y sus alturas

la modalidad la presenta el sistema, no una vista a pantalla completa hecha a mano
Pendiente

Cuándo aplicacualquier vista modal: formularios, filtros, detalles

Qué estás comprobandoLas vistas modales usan la presentación del sistema. Eso trae el indicador de arrastre, el cierre deslizando, el comportamiento al girar el móvil y, desde iOS 15, alturas parciales. Reconstruir una hoja a mano significa reconstruir todo eso, y normalmente solo se reconstruye la parte que se ve.

La prueba, con el móvil en la mano

Abre la hoja, escribe algo dentro y arrástrala hacia abajo sin guardar. Observa qué pasa con lo que habías escrito.

Pasa si

La hoja se arrastra, respeta las alturas declaradas y, si hay trabajo sin guardar, pregunta antes de descartarlo.

En React Native o Expo

En React Native, presentationStyle="pageSheet" en Modal da la presentación nativa. Para alturas parciales, react-native-screens expone opciones de hoja (sheetAllowedDetents) en native-stack, que expo-router acepta en las opciones de pantalla.

Detalle técnico

Una hoja a media altura, declarada donde se declara la pantalla:

<Stack.Screen
  name="filtros"
  options={{
    presentation: 'formSheet',
    sheetAllowedDetents: [0.5, 1],
    sheetGrabberVisible: true,
  }}
/>

Comprueba la versión de react-native-screens de tu proyecto antes de dar por hecho que acepta estas opciones: no están en las versiones antiguas.

04Navegación

Controles y materiales del sistema, no versiones imitadas

si iOS ya resuelve ese patrón, no lo reconstruyas desde cero
Pendiente

Cuándo aplicaswitches, pickers, menús, barras y superficies translúcidas

Qué estás comprobandoLos controles del sistema traen sus estados, su accesibilidad y su comportamiento en las dos apariencias. Los materiales del sistema son translucidez con vibrancy que se adapta sola a lo que hay detrás y al modo claro u oscuro. Un color semitransparente fijo no hace eso.

La prueba, con el móvil en la mano

Pon el móvil en modo oscuro y vuelve a mirar cada superficie translúcida. Después haz scroll de contenido con colores fuertes por debajo de ella.

Pasa si

La superficie cambia con lo que tiene detrás y sigue siendo legible en las dos apariencias sin tocar nada.

En React Native o Expo

expo-blur expone BlurView con tint y intensity, y en iOS se apoya en el efecto visual del sistema. En Android el desenfoque es una aproximación y conviene decidir qué se ve ahí en lugar de dejarlo al azar.

Detalle técnico

Lo que hay que buscar en el repo antes de dar este punto por bueno:

# superficies translúcidas pintadas a mano
grep -rn "rgba(255, *255, *255, *0\." src app
grep -rn "rgba(0, *0, *0, *0\." src app
05Navegación

SF Symbols cuando el sistema ya tiene ese icono

están hechos para alinearse con el texto, escalar con él y tener sus mismos pesos
Pendiente

Cuándo aplicaiconos de acciones, tabs y barras en iOS

Qué estás comprobandoSF Symbols está diseñado junto a la tipografía del sistema: los símbolos comparten métricas con el texto, escalan con él, tienen pesos y variantes de relleno, y se adaptan a la dirección de lectura. Un pack genérico es una imagen al lado de una palabra, y se nota sobre todo en los tamaños grandes.

La prueba, con el móvil en la mano

Sube el tamaño de texto del sistema al máximo y mira los iconos que van junto a una etiqueta. O crecen con ella o se quedan pequeños y descolocados.

Pasa si

El icono y su etiqueta crecen juntos y mantienen su alineación vertical.

En React Native o Expo

expo-symbols expone SymbolView, solo en iOS 15 y posteriores. Los packs tipo Feather o Ionicons funcionan en las dos plataformas, pero no escalan con Dynamic Type ni traen los pesos del sistema. Una app multiplataforma acaba con dos juegos y una capa fina que elige según Platform.OS.

Detalle técnico

Antes de exportarlos a otro sitio, lee la licencia: Apple circunscribe el uso de SF Symbols a sus plataformas y no permite usarlos como logotipo ni como elemento de marca. Es el punto de esta lista con letra pequeña legal, y no es una opinión de diseño.

// iOS usa el símbolo del sistema, el resto el pack
import { Platform } from 'react-native';
import { SymbolView } from 'expo-symbols';
import Feather from '@expo/vector-icons/Feather';

export function Icono({ ios, otro, size, color }) {
  return Platform.OS === 'ios'
    ? <SymbolView name={ios} size={size} tintColor={color} />
    : <Feather name={otro} size={size} color={color} />;
}
06Interacción

Áreas táctiles de 44 puntos como mínimo

el icono puede ser pequeño; la zona donde se toca, no
Pendiente

Cuándo aplicatodo lo que se pueda tocar

Qué estás comprobandoApple recomienda una zona tocable de al menos 44 por 44 puntos. El dibujo puede medir mucho menos: lo que tiene que cumplir esa medida es el área que responde. Un icono minúsculo que exige puntería se siente peor que uno feo, y no hay forma de disimularlo.

La prueba, con el móvil en la mano

Usa la app de pie, con una mano, con el pulgar, andando si puedes. Ahí aparecen los fallos. Con el índice y el móvil apoyado en la mesa no aparece ninguno.

Pasa si

Todo lo tocable responde a la primera con el pulgar, y dos controles seguidos no se pisan cuando fallas un poco.

En React Native o Expo

hitSlop en Pressable amplía el área sin tocar el diseño. minHeight y minWidth cambian lo que se ve; hitSlop cambia lo que se toca. Para un icono pequeño dentro de una fila, hitSlop es casi siempre la respuesta correcta.

Detalle técnico

Un icono de 20 puntos con área de 44:

<Pressable
  hitSlop={12}          // 20 + 12 + 12 = 44
  onPress={borrar}
  accessibilityRole="button"
  accessibilityLabel="Borrar"
>
  <Feather name="trash-2" size={20} color={colors.muted} />
</Pressable>

Dos controles pegados con hitSlop generoso pueden solaparse. Si pasa, gana el que esté más arriba en el árbol, y no es necesariamente el que la persona quería tocar: ahí hay que separar, no ampliar.

07Interacción

El botón cambia de estado en cuanto lo tocas

el feedback visual empieza al tocar; la acción se confirma al soltar dentro
Pendiente

Cuándo aplicabotones, filas y cualquier control propio

Qué estás comprobandoSon dos momentos distintos y los dos importan. El estado visual aparece al posar el dedo, para que se vea que el control ha recibido el toque. La acción se ejecuta al levantar el dedo dentro del control, que es exactamente lo que significa touchUpInside en UIKit. Un botón propio sin estado pulsado parece que no responde.

La prueba, con el móvil en la mano

Toca un botón y mantén el dedo sin soltar: tiene que verse pulsado. Después, sin levantar, arrastra el dedo fuera del botón y suelta ahí.

Pasa si

El estado visual aparece al tocar, y al soltar fuera no ocurre nada.

En React Native o Expo

Pressable separa onPressIn (estado visual) de onPress (acción), y onPress ya se comporta como touchUpInside: no dispara si sueltas fuera. TouchableOpacity anima la opacidad y poco más, así que para un control con varios estados se queda corto.

Detalle técnico

El estado se pinta con la función que Pressable pasa a style:

<Pressable
  onPress={guardar}
  style={({ pressed }) => [
    styles.boton,
    pressed && styles.botonPulsado,
  ]}
>
  <Text>Guardar</Text>
</Pressable>

Para auditarlo rápido, busca acciones colgadas del evento de entrada:

grep -rn "onPressIn" src app
08Interacción

Háptica solo cuando confirma algo

acompaña a una acción o a un cambio real, nunca de adorno
Pendiente

Cuándo aplicaconfirmaciones, cambios de selección y errores

Qué estás comprobandoLa háptica es un canal de confirmación, no un efecto. Apple pide que corresponda a una acción o a un cambio visible y avisa expresamente de no sobreusarla. Una app que vibra en cada toque acaba con la háptica del sistema desactivada, y ahí pierde también las vibraciones que sí importaban.

La prueba, con el móvil en la mano

Recorre un flujo completo contando las vibraciones. Por cada una, di en voz alta qué confirmó. Las que no sepas explicar, sobran.

Pasa si

Cada vibración corresponde a algo que ocurrió, y el tipo de vibración distingue un cambio de selección de un éxito y de un error.

En React Native o Expo

expo-haptics separa tres familias y la diferencia no es cosmética: selectionAsync para un cambio de selección, impactAsync para un golpe físico con su intensidad, y notificationAsync para éxito, aviso o error. Usar impactAsync para todo borra esa diferencia.

Detalle técnico

Cada familia en su sitio:

import * as Haptics from 'expo-haptics';

// cambia el elemento seleccionado
Haptics.selectionAsync();

// la acción terminó bien o mal
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success);
Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error);

// algo chocó, encajó o se soltó
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);

Si la persona tiene la háptica del sistema desactivada en Ajustes, no se reproduce nada. Por eso nunca puede ser el único canal que confirme una acción: siempre tiene que haber algo que también se vea.

09Interacción

El gesto sigue al dedo y se puede interrumpir

el objeto se mueve mientras dura el gesto, no después de soltarlo
Pendiente

Cuándo aplicaarrastrar, deslizar, paneles y hojas

Qué estás comprobandoMientras el dedo está en la pantalla, lo que se arrastra se mueve con él. Y la animación que viene después se puede coger a mitad. Esa es la propiedad que separa una interfaz que se siente viva de una que reproduce vídeos de animación cuando tú ya has terminado de hacer el gesto.

La prueba, con el móvil en la mano

Empieza un gesto, suéltalo a mitad y, mientras la animación sigue corriendo, vuelve a poner el dedo encima e intenta llevártelo al otro lado.

Pasa si

El objeto responde al segundo toque sin esperar a que termine la animación anterior.

En React Native o Expo

Con react-native-gesture-handler y Reanimated, el gesto y su animación corren en el hilo de UI y son interrumpibles. Con PanResponder y Animated sin native driver, cada fotograma del gesto pasa por el hilo de JavaScript, así que el punto 12 de esta lista y este son el mismo problema visto desde dos sitios.

Detalle técnico

Lo que hay que poder responder de cada gesto de tu app: qué pasa si se suelta a mitad, qué pasa si se suelta muy rápido, y qué pasa si se vuelve a tocar mientras se está moviendo. Si la tercera no tiene respuesta, no es interrumpible.

10Movimiento

Springs después de un gesto, para conservar la velocidad

un spring no es un rebote: es continuidad de posición y de velocidad
Pendiente

Cuándo aplicacualquier movimiento que venga de un gesto

Qué estás comprobandoCuando sueltas algo que venías arrastrando, el movimiento tiene que seguir desde la velocidad que traía. Eso es lo que da un spring, y es la razón por la que buena parte de iOS los usa. El rebote es opcional: hay muchos springs del sistema que no rebotan nada y siguen siendo springs.

La prueba, con el móvil en la mano

Arrastra un panel y suéltalo a media velocidad, no desde parado. Mira el momento exacto en el que levantas el dedo.

Pasa si

El movimiento continúa desde donde iba y a la velocidad que llevaba, sin pararse y volver a arrancar.

En React Native o Expo

Animated.spring acepta velocity, que es donde se le pasa la velocidad con la que venía el gesto. En Reanimated, withSpring la recibe directamente del gesto. Si en tu código el spring no recibe velocidad de ningún sitio, el corte está ahí.

Detalle técnico

La velocidad del gesto entra en la animación, no se descarta:

// Animated: la velocidad viene del gesto que acaba de terminar
Animated.spring(x, {
  toValue: destino,
  velocity: gesto.vx,
  useNativeDriver: true,
}).start();
11Movimiento

Los cambios importantes no saltan: tienen continuidad

se anima lo que necesita continuidad, no todo lo que cambia
Pendiente

Cuándo aplicalistas que cambian, contadores, apariciones y borrados

Qué estás comprobandoCuando un cambio rompe la continuidad visual y la persona pierde de vista dónde estaba algo, ese cambio se anima. Cuando el movimiento no comunica nada, no. Apple es explícita en las dos direcciones: el movimiento tiene que aportar información, y el movimiento gratuito es un problema, no un extra.

La prueba, con el móvil en la mano

Provoca el cambio y pregúntate si podrías seguir con la vista de dónde a dónde se movió cada cosa. Si un elemento desaparece de un sitio y aparece en otro sin trayectoria, ahí falta continuidad.

Pasa si

Los cambios que mueven contenido se pueden seguir con la vista y los que no aportan nada ocurren sin ceremonia.

En React Native o Expo

En React Native, LayoutAnimation y las transiciones de Reanimated resuelven la mayoría de estos casos. Antes de elegir herramienta conviene tener escrito qué cambios de tu app necesitan continuidad: normalmente son muchos menos de los que se animan.

Consulta la fuente

Apple · Motion
12Movimiento

La animación crítica no espera al hilo de JavaScript

si JavaScript está ocupado, el movimiento no debería empezar a dar tirones
Pendiente

Cuándo aplicaapps de React Native con animación durante trabajo pesado

Qué estás comprobandoEn React Native una animación puede correr en el hilo de UI y seguir fluida aunque el hilo de JavaScript esté ocupado procesando una respuesta, recorriendo una lista larga o montando una pantalla. Si la animación depende del hilo de JS, cualquier trabajo pesado se convierte en tirones, y los tirones son exactamente lo que hace que una app se sienta como una capa encima del móvil.

La prueba, con el móvil en la mano

Bloquea el hilo de JavaScript a propósito durante un segundo mientras la animación corre. Es la prueba más rápida y más honesta que existe para este punto, y se hace en dos minutos.

Pasa si

La animación sigue moviéndose durante todo el bloqueo. Si se congela y salta al final, estaba en el hilo de JavaScript.

En React Native o Expo

Reanimated ejecuta worklets en el hilo de UI. Animated con useNativeDriver: true envía la animación al lado nativo al arrancar. La auditoría de treinta segundos es contar cuántos false hay en el repo y mirar uno por uno si están justificados.

Detalle técnico

Pega esto detrás de un botón, lanza la animación y púlsalo:

// bloquea el hilo de JS un segundo entero
function bloquear() {
  const fin = Date.now() + 1000;
  while (Date.now() < fin) {}
}

Y el recuento que dice dónde estás:

grep -rc "useNativeDriver: true" src app | grep -v ":0" | wc -l
grep -rn "useNativeDriver: false" src app
13Sistema

El layout se mueve con el teclado, a la vez

teclado e interfaz son una sola transición, no dos que casi coinciden
Pendiente

Cuándo aplicacualquier pantalla con un campo de texto

Qué estás comprobandoCuando aparece el teclado, la interfaz se reacomoda con la misma duración y la misma curva. iOS anuncia ambas cosas junto con el tamaño del teclado. Cuando no se usan, el contenido llega un poco tarde o pega un salto al final, y ese desajuste es de los que se notan sin saber nombrarlos.

La prueba, con el móvil en la mano

Pon el cursor en un campo pegado al borde inferior. Abre y cierra el teclado varias veces seguidas, y prueba también a cambiar de un campo a otro con el teclado ya abierto.

Pasa si

El campo queda siempre visible, el contenido acompaña al teclado durante todo el recorrido y no hay ningún salto al final.

En React Native o Expo

KeyboardAvoidingView de React Native reacciona al teclado pero no sincroniza con su curva, y ese es el origen de casi todos los saltos. react-native-keyboard-controller expone la animación fotograma a fotograma y permite atar el layout al recorrido real.

Detalle técnico

iOS anima el teclado con una curva propia que no coincide con las curvas estándar de animación, así que reproducirla «a ojo» con una curva normal no termina de encajar nunca. Esa es la razón técnica de que el ajuste manual parezca siempre casi bueno.

14Sistema

Ninguna pantalla se queda muda mientras carga

contenido parcial, placeholder o indicador, según el caso
Pendiente

Cuándo aplicacualquier pantalla que espere a la red o al disco

Qué estás comprobandoMientras se espera tiene que haber algo que explique qué está pasando. Cuál de las tres formas depende del caso y no hay una que gane siempre: Apple sigue recomendando indicadores de progreso para procesos indeterminados y para espacios pequeños, y un placeholder tiene sentido cuando puedes anticipar la forma de lo que va a aparecer.

La prueba, con el móvil en la mano

Limita la red a una conexión lenta y recorre la app entera desde cero, sin caché. Anota cada pantalla que se quede en blanco más de un segundo sin decir nada.

Pasa si

Ninguna pantalla se queda muda, y cuando algo falla se distingue de cuando algo todavía está cargando.

Detalle técnico

El estado vacío y el estado de error son estados distintos del estado de carga, y suelen ser los dos que faltan. Una lista vacía porque no hay nada y una lista vacía porque la petición falló no pueden verse igual.

15Sistema

Safe areas medidas, nunca un padding a ojo

las zonas seguras se leen del sistema porque cambian solas
Pendiente

Cuándo aplicatodas las pantallas, y especialmente las barras fijas

Qué estás comprobandoLas zonas seguras dependen del modelo, de la orientación y del estado del sistema. La barra de estado crece cuando hay una llamada activa o una grabación de pantalla, y el indicador de inicio no está en el mismo sitio en horizontal. Ignorarlas hace que la experiencia no se sienta de la plataforma y además tapa cosas.

La prueba, con el móvil en la mano

Prueba en un modelo con Dynamic Island y en uno sin notch. Después gira el móvil. Después inicia una grabación de pantalla y mira la parte de arriba.

Pasa si

Nada queda tapado ni descolocado en ninguno de esos cuatro estados, y no hay ningún número mágico en el código que lo sostenga.

En React Native o Expo

react-native-safe-area-context da useSafeAreaInsets, que se actualiza cuando los insets cambian. Una constante no. Si tu barra de tabs o tu cabecera tiene una altura fija por plataforma, ese es el sitio donde mirar primero.

Detalle técnico

La auditoría es corta y encuentra casi siempre algo:

# alturas y paddings fijos por plataforma
grep -rn "Platform.OS === 'ios' ?" src app | grep -iE "height|padding"

Un inset se suma al padding propio, no lo sustituye:

const insets = useSafeAreaInsets();

<View style={{ paddingBottom: insets.bottom + 12 }}>
16Sistema

El texto escala con Dynamic Type y los colores se adaptan al sistema

dos preferencias que la persona ya eligió antes de abrir tu app
Pendiente

Cuándo aplicatodo el texto y todos los colores de la app

Qué estás comprobandoSon dos cosas distintas con el mismo origen: la persona ya ha decidido a qué tamaño lee y en qué apariencia quiere las cosas, y lo ha decidido para todo el móvil. Los colores pueden ser de marca; lo que hace falta es que cada uno tenga su variante para claro y para oscuro, no que no exista ningún color propio.

La prueba, con el móvil en la mano

Ajustes, Accesibilidad, Pantalla y tamaño del texto, Texto más grande, y sube hasta el máximo. Después cambia el móvil a modo oscuro. Recorre la app con las dos cosas puestas a la vez, que es como la va a usar alguna gente todos los días.

Pasa si

No se corta ningún texto, nada se solapa, ningún botón pierde su etiqueta y ningún color queda ilegible en oscuro.

En React Native o Expo

En React Native, Text escala por defecto. maxFontSizeMultiplier pone un tope razonable sin desactivar el escalado, y es el término medio honesto cuando un layout no aguanta el máximo absoluto. Para el color, useColorScheme más un tema por apariencia; y en app.json, userInterfaceStyle: "automatic" para que la app siga al sistema en vez de imponer una.

Detalle técnico

Un tope, no un apagado:

// mal: rompe la accesibilidad para salvar el diseño
<Text allowsFontScaling={false}>Guardar</Text>

// mejor: escala, con un límite que el layout aguanta
<Text maxFontSizeMultiplier={1.4}>Guardar</Text>

Y la búsqueda que dice si este punto está resuelto o no:

grep -rn "allowsFontScaling={false}" src app
17Sistema

Si el usuario activa Reduce Motion, reduces o sustituyes el movimiento

es accesibilidad, no un extra: hay gente a la que el movimiento le sienta mal
Pendiente

Cuándo aplicacualquier app con animación, autoplay o paralaje

Qué estás comprobandoCon Reducir movimiento activado se reduce o se sustituye el movimiento que puede resultar incómodo: zooms, desplazamientos grandes, paralaje, rebotes. El cross-fade es una sustitución habitual y es lo que expresa la preferencia prefersCrossFadeTransitions, pero es un ejemplo de implementación correcta, no la única. Lo que no vale es no hacer nada.

La prueba, con el móvil en la mano

Ajustes, Accesibilidad, Movimiento, Reducir movimiento. Recorre la app entera con la opción puesta. Cuenta también los autoplay, los bucles decorativos y los fondos que se mueven solos.

Pasa si

No queda ninguna transición grande de posición o de escala, la app se sigue pudiendo usar igual y no se pierde ninguna información por el camino.

En React Native o Expo

AccessibilityInfo.isReduceMotionEnabled() da el valor actual y el evento reduceMotionChanged avisa de los cambios mientras la app está abierta. Reanimated tiene además ReducedMotionConfig para decidir el comportamiento por defecto de sus animaciones.

Detalle técnico

El valor se lee y además se escucha:

import { AccessibilityInfo } from 'react-native';

AccessibilityInfo.isReduceMotionEnabled().then(setReduce);
const sub = AccessibilityInfo.addEventListener(
  'reduceMotionChanged',
  setReduce,
);
return () => sub.remove();

Una preferencia propia dentro de la app, además de la del sistema, es una buena idea: hay gente que quiere menos movimiento solo en tu app y no en todo el móvil. Lo que no puede pasar es que la propia sustituya a la del sistema.

Si solo tienes diez minutos

siete ajustes y gestos que no necesitan tocar código y que destapan la mayoría de los fallos de esta lista en unos diez minutos:

no es una priorización universal. es la ruta más corta desde cero hasta saber qué tienes roto.

Cómo usar esta guía

Para cada punto guarda tres cosas: en qué modelo y versión lo probaste, qué hiciste exactamente, y qué pasó. «Parece correcto» no es un estado.

Casi todas las pruebas de esta lista se hacen con el móvil en la mano y sin abrir el editor. Esa es la idea: lo que se comprueba leyendo código son las causas, no los síntomas, y los síntomas se ven antes.

Un punto puede no aplicarte, y eso es una respuesta legítima siempre que escribas el motivo. Una app sin gestos de arrastre no tiene por qué inventárselos.

En las fichas, «Comprobado» es que lo probaste y pasó, «Por corregir» es que lo probaste y falló, y «Pendiente» es que todavía no lo has probado.

esto no certifica que una app sea accesible ni que vaya a pasar la revisión de la App Store. es una lista de comportamiento, no de rendimiento.

no tienes que leerla entera. mira qué te aplica, abre solo esos puntos y haz la prueba del móvil antes de marcar nada.

Lo que esta lista no cubre

Esta lista va de comportamiento: de que la app haga lo que iOS ya enseñó a hacer a quien la usa. Deliberadamente no cubre:

Fuentes generales

Apple · Human Interface Guidelines, diseñar para iOS

Apple · Movimiento

Apple · Accesibilidad

React Native · Animated

Fuentes consultadas y enlaces comprobados el 21 de septiembre de 2026. Las guías de Apple cambian: si un enlace ya no dice lo que aquí se resume, manda la guía.

Una app no se siente nativa porque parezca iOS.

Se siente nativa cuando se comporta como la persona espera que se comporte iOS, y eso casi nunca se decide en el diseño. Se decide en los detalles que solo aparecen cuando coges el móvil y lo usas de pie, con una mano, con el texto grande y con el movimiento reducido.