Saltar al contenido
Development

Cómo localizar el aviso de permiso de Apple Music (y el desvío de pruebas en dispositivo que eso implicó)

Por Victor Da Luz
swiftiosi18ndev-logdeep-cut-atlas

Una sola cadena de texto. Eso era todo lo que este issue debía tocar: el aviso de permiso del sistema que Apple Music muestra cuando Deep Cut Atlas pide acceso a la biblioteca. Dos horas después había reiniciado todo el estado de privacidad de un iPhone y reescrito una buena parte de mi base de conocimiento sobre pruebas en dispositivo. Así es como una tarea de localización de una línea se convirtió en eso.

El problema

La adaptación retroactiva a String Catalog le dio al texto de la interfaz de la app un lugar donde traducirse. Pero NSAppleMusicUsageDescription, el texto del diálogo real a nivel de sistema operativo que dice “Deep Cut wants to access Apple Music”, vive en otro lugar: es un ajuste de compilación (INFOPLIST_KEY_NSAppleMusicUsageDescription), no un literal de cadena en el código fuente. La adaptación nunca lo tocó. Publicar la app tal cual significa que la primera interacción de un usuario hispanohablante con ella es un diálogo de permiso en inglés.

Antes de tocar nada, había encontrado dos hilos del foro de Apple Developer que se contradecían por completo sobre si un String Catalog puede siquiera localizar una clave respaldada por un ajuste de compilación sin antes moverla a un archivo Info.plist real. En vez de elegir uno y confiar en que fuera el correcto, hice la prueba de dos líneas: crear un InfoPlist.xcstrings vacío, compilar una vez, y ver qué pasaba.

Lo que pasó en realidad

Simplemente funcionó. La compilación extrajo NSAppleMusicUsageDescription directamente del ajuste de compilación hacia el nuevo catálogo, sin necesidad de migrar a Info.plist, y de paso también extrajo CFBundleDisplayName y CFBundleName como bonus. Uno de los hilos del foro tenía razón para esta versión de Xcode, el otro no. Menos mal que lo comprobé en vez de adivinar.

Traducir fue la parte fácil, español (Latinoamérica, no España) y portugués brasileño, ambos revisados mientras se renderizaban en vivo en el dispositivo. La parte difícil fue demostrar que realmente funcionaba, porque “realmente funcionaba” para un diálogo de permiso del sistema significa provocar un estado que el sistema operativo no permite simular: una solicitud de permiso nunca antes respondida, en un idioma de dispositivo específico.

El desvío de las pruebas en dispositivo

Este proyecto tiene todo un montaje para controlar un iPhone real de forma remota vía iPhone Mirroring, construido justo para este tipo de prueba. Lo que no tenía era la certeza de estar controlando el teléfono correcto. El build scheme de Xcode apuntaba a un dispositivo; la sesión de mirroring estaba conectada, en silencio, a otro distinto. Ya había corrido una compilación y una desinstalación contra el teléfono equivocado antes de que un aviso de “Unlock Your iPhone” que nombraba al iPhone 13 de repuesto, visible en una captura de pantalla, me hiciera notar que había estado automatizando dos dispositivos físicos distintos sin darme cuenta. Lección: comprobar devicectl list devices contra lo que sea que diga el mirroring que tiene conectado, cada vez, antes de correr algo destructivo.

Una vez con el dispositivo correcto, la siguiente sorpresa: desinstalar y reinstalar la app no reinicia su permiso de Apple Music. Supuse que una instalación nueva significaba un estado “aún no decidido”, igual que suele pasar con otras cosas. No es así, iOS ata esa decisión al bundle ID a nivel del sistema operativo, no a una instalación en particular. La única forma de que el aviso volviera a aparecer fue la opción nuclear: Settings → General → Transfer or Reset iPhone → Reset → Reset Location & Privacy, que borra los permisos de privacidad de todas las apps del dispositivo, no solo de esta. Eso requiere el código de acceso del dispositivo, que no se puede escribir de forma remota, así que esa parte tuvo que hacerse en persona.

Y ese reinicio provoca un restart completo, después del cual la sesión de mirroring vuelve en estado “paused” pero de una forma que (a diferencia del estado paused habitual) no se recupera sola, necesita un desbloqueo real por Face ID o código en el teléfono. Tres comportamientos del dispositivo en una sola tarde que no eran pistas falsas, que no los causó mi automatización, que simplemente eran reales, ninguno documentado en ningún lugar que hubiera leído antes de toparme con ellos.

Qué haría distinto

Nada respecto al trabajo de localización en sí, ahí el instinto de “probar lo barato antes de asumir” rindió frutos. Lo que costó tiempo fueron las suposiciones sobre las pruebas en dispositivo: confié en un modelo mental (desinstalar reinicia el estado, el mirroring siempre sigue al target de compilación) que resultó equivocado en ambos puntos. Esta vez lo estoy dejando por escrito para que en la próxima sesión sea un dato conocido en vez de un redescubrimiento.

Lecturas relacionadas