Documentación de Touch on Mac: migración, juegos y HIG
¿Qué publicó Apple para Touch on Mac después de las charlas? En la noche del 28 de octubre, el artículo de AppKit “Adopting gesture recognizers in your app”, que devolvía 404 al mediodía, ya estaba publicado con una tabla de cuatro filas que le da un siguiente paso a cada tipo de código de manejo del mouse; un segundo artículo, “Handling AppKit gestures”, documenta las vistas exclusivas, un tiempo límite de dos segundos para los reconocedores atascados y dos ajustes de depuración; una nota técnica nueva, TN3216, cubre los juegos y el orden de los eventos de mouse emulados; y tres páginas de las Human Interface Guidelines llevan entradas en su registro de cambios con fecha del 28 de octubre sobre el toque en la Mac.1234 El mosaico “Documentation resources” de la página de aterrizaje ahora enlaza el artículo y el ejemplo de file promises, su mosaico “Design resources” enlaza las tres páginas de las HIG, y el mosaico de la beta de Xcode 27.3 sigue diciendo “Coming soon” (próximamente).5
TL;DR: El post para desarrolladores, publicado a la 1:47 p.m. PT del 28 de octubre, partía de tres Tech Talks y de una nota técnica de junio; el artículo de AppKit que necesitaba devolvía 404 al mediodía. A las 9:51 p.m. ese artículo estaba publicado, con dos documentos más a su lado. El artículo de migración clasifica tu código en cuatro filas: los controles estándar y los reconocedores no necesitan nada; las sobrescrituras de mouseDown(with:) y los bucles de seguimiento reciben emulación de un solo dedo y deberían convertirse en reconocedores; una subclase de control que sobrescribe un método del mouse vuelve a un bucle de seguimiento; un reconocedor propio que nunca termina retiene su vista exclusiva.1 La regla detrás de la tercera fila es que AppKit decide según si el método está sobrescrito, “not on what the override does” (no según lo que hace la sobrescritura), así que llamar a super no ayuda.1 Un NSPanGestureRecognizer solo táctil con buttonMask en 0 junto a un bucle que conservas ahora está documentado, con la consecuencia precisa de dejar la máscara como está: un arrastre del mouse inicia el reconocedor y tu mouseDown(with:) nunca se ejecuta.1 Las vistas exclusivas están activadas por defecto cuando falta la clave de Info.plist, y la plantilla macOS App de Xcode escribe la clave como NO solo para un proyecto cuyo ciclo de vida de app es el app delegate de AppKit, no para el ciclo de vida de SwiftUI que viene por defecto; eso lo leí en las plantillas de Xcode 27.1 y de la beta de 27.2 en esta Mac.27 Un reconocedor atascado se cancela dos segundos después de que termina la interacción, y mientras depuras, un ajuste de UserDefaults provoca un fallo cuando falta una sobrescritura y otro reemplaza la cancelación silenciosa por un fallo.2 La nota técnica para juegos lista los eventos de mouse exactos que producen un toque y un arrastre, les dice a los motores que no sumen deltas y explica que cada superficie táctil aparece como un GCMouse emulado adicional hasta que se configura NSIsTouchNative.3 Las HIG llaman al toque “a secondary input mode” (un modo de entrada secundario), piden hacer “tap targets bigger, not the controls themselves” (más grandes las áreas de toque, no los controles) y agregan seis reglas para los controles que solo aparecían con el hover.4 Lo que todavía no está: la beta de Xcode 27.3 y los tres interruptores de gestos del sistema, que devuelven 404 a las 9:51 p.m. PT.56
Lo que llegó, y cuándo
Cada punto de abajo lleva la hora en que lo revisé, porque las marcas del propio servidor no coinciden con lo que las páginas mostraban durante el día; el post en vivo tiene el registro del día, con una fila tardía para esta publicación.
- El artículo de AppKit. “Adopting gesture recognizers in your app” devolvía 404 desde el endpoint de documentación de Apple al mediodía PT; a las 9:51 p.m. estaba publicado, servido con una marca de modificación de las 2:30 p.m. PT, listado en la sección Essentials de AppKit, después de “Adopting Liquid Glass”, y bajo “Gesture handling” en la página Gestures del framework, y la primera captura de la Wayback Machine es de las 6:08 p.m. PT (los comprobantes están en las fuentes).1 La página del Tech Talk “Bring Touch to your Mac app” todavía no lo enlaza.5
- Un segundo artículo. “Handling AppKit gestures” está a su lado en la página Gestures. No tiene historial de revisiones, la nota técnica de junio no lo enlaza, la Wayback Machine no tiene ninguna captura y su marca del servidor es de las 10:35 p.m. PT del 27 de octubre; no estaba entre las páginas que revisé al mediodía, así que no puedo decir cuándo se publicó.2
- Una nota técnica nueva. “TN3216: Supporting touch input for games on macOS” tiene un historial de revisiones de una línea, “2026-10-28 First published”, y una marca del servidor de las 2:09 p.m. PT.3
- Tres páginas de las HIG. “Designing for macOS”, “Gestures” y “Pointing devices” ahora terminan con filas en el registro de cambios fechadas el 28 de octubre de 2026. Las copias que descargué al mediodía no tenían nada del texto nuevo; las descargadas a las 9:51 p.m. sí. El encabezado de modificación del servidor en la nueva página “Gestures” dice 5:11 UTC del 27 de octubre, antes que la copia del mediodía que no tenía el texto; reporto ambos hechos y ningún minuto de publicación.4
- La página de aterrizaje. A las 11:49 a.m., los tres mosaicos bajo “Tools and documentation” decían “Coming soon”, con sus enlaces guardados en comentarios HTML. A las 9:51 p.m., el mosaico “Documentation resources” (“Learn how to build and optimize your app for touch input.”) enlaza el artículo y el ejemplo de file promises, y el mosaico “Design resources” (“Create experiences that take advantage of touch user interface patterns.”) enlaza las tres páginas de las HIG y Apple Design Resources, cuya lista de marcos de producto incluye “MacBook Pro PROLED XDR Display”. El mosaico “Xcode 27.3 beta” sigue diciendo “Coming soon”, y el comentario HTML
TK skillsigue en la página.5 - El ejemplo. La página de “Supporting drag and drop through file promises” ahora dice de sus reconocedores que “The recognizers work the same way with a pointer or with direct touches on a Mac with a touch display.” (los reconocedores funcionan igual con un puntero o con toques directos en una Mac con pantalla táctil).6
- Nada más se movió. El elemento más nuevo del feed de noticias para desarrolladores sigue siendo “Say hello to Touch on Mac” a las 10:00:55 a.m. PDT; los elementos más nuevos del feed de versiones son las versiones puntuales del 26 de octubre, sin beta de Xcode 27.3 ni de macOS 27.3; el listado de Tech Talks tiene las mismas tres charlas; la página de ActivityKit lleva una marca del 8 de octubre y sigue sin listar la plataforma macOS.56
El post de la tarde decía que las reglas escritas eran las charlas y la nota técnica de junio. Este post trata de lo que agregan las reglas escritas, y corrige el post de la tarde donde la revisión de la noche encontró información distinta; la lista está al final.
La tabla que clasifica tu código
La sección “Prepare your app for a Mac with a touch display” del artículo es una tabla con tres columnas (manejo de la entrada, comportamiento con el toque y siguiente paso) y cuatro filas. La columna de comportamiento de Apple va citada; las otras dos van condensadas, salvo el siguiente paso de la tercera fila, que es la redacción de Apple:1
| Tu código | “Behavior with touch” según Apple | Siguiente paso |
|---|---|---|
| Controles estándar de AppKit y reconocedores de gestos | “Works with touch, with no changes needed.” | Probar. |
Sobrescrituras de mouseDown(with:) y bucles de seguimiento |
“AppKit emulates a single-finger tap or drag, but supports multi-finger gestures and individual touches only through gesture recognizers.” | Reemplazarlos por reconocedores. |
Subclases de NSButton, NSSlider y otras vistas de AppKit que sobrescriben métodos de manejo del mouse |
“AppKit falls back to a tracking loop.” | “Replace the overrides with target-action pairs for tracking events.” |
Subclases propias de NSGestureRecognizer |
“A recognizer that never reaches a terminal state keeps its exclusive view, which can make your app appear unresponsive.” | Manejar la cancelación y llegar a un estado terminal. |
La prueba que sigue a la tabla son cuatro líneas que vale la pena ejecutar tal cual: “Controls respond to a tap, and buttons highlight and activate as expected”, “Dragging a view, selecting text, and scrolling work with one finger”, “Pinch and rotate work where your app supports them” y “Small targets, such as resize grips and grab handles, are easy to hit with a finger.” (los controles responden a un toque y los botones se resaltan y se activan como se espera; arrastrar una vista, seleccionar texto y desplazarse funcionan con un dedo; pellizcar y rotar funcionan donde la app los admite; los objetivos pequeños, como los tiradores de redimensionar y de agarre, son fáciles de alcanzar con un dedo).1 La lista de búsqueda amplía a cinco símbolos la auditoría de sobrescrituras de mouseDown(with:) que proponía la charla: “search your project for nextEvent(matching:until:inMode:dequeue:) and trackEvents(matching:timeout:mode:handler:), because tracking loops typically call these methods. Also search for mouseDown(with:) and mouseDragged(with:) overrides, and for uses of currentEvent in control action methods.”1 La última comprobación es la que corresponde a la cuarta fila: “confirm that your app stays responsive after a touch interaction ends, and that every gesture recognizer action method handles the NSGestureRecognizer.State.cancelled state.” (confirma que la app sigue respondiendo cuando termina una interacción táctil y que cada método de acción de un reconocedor maneja el estado cancelled).1
Dos frases del Overview resuelven una pregunta que la charla dejaba a la inferencia. La emulación es de un solo dedo: “AppKit emulates mouse events from a single finger tapping or dragging”, y “Relying on this emulation, however, means you miss out on multi-finger gestures, like pinch and rotate, that gesture recognizers handle.” (depender de esta emulación significa perderte los gestos de varios dedos, como pellizcar y rotar, que manejan los reconocedores).1 Y el banco de pruebas es el que nombró la charla, ahora en un consejo: “Use Sidecar to test touch input without a Mac that has a touch display”, porque “AppKit then routes touch on its screen the same way it does on a Mac with a touch display.” (AppKit dirige entonces el toque en su pantalla igual que en una Mac con pantalla táctil).1
La regla de las sobrescrituras
La tercera fila de la tabla tiene detrás una regla que ni la charla ni la nota técnica de junio decían con esta claridad. Si creas una subclase de NSButton y sobrescribes mouseDown(with:) para agregar una animación de resaltado, “AppKit treats your override as a replacement for its own click handling, rather than as a side effect” (AppKit trata tu sobrescritura como un reemplazo de su propio manejo del clic, no como un efecto secundario), y el botón vuelve a un bucle de seguimiento.1 Luego: “AppKit decides based on whether your subclass overrides the method, not on what the override does”, así que la vuelta al bucle de seguimiento se aplica aunque la sobrescritura llame a super.mouseDown(with:), y “Overriding mouseDragged(with:) or mouseUp(with:) has the same effect.”1
El reemplazo es el patrón de eventos de control de la charla, y el listado del artículo difiere del de la charla en un detalle que vale la pena copiar. El listado de la charla en el 2:24 registraba una acción para [.trackingExited, .trackingEndedInside] y otra aparte para .trackingCancelled; el conjunto de ocultación del artículo es [.trackingExited, .trackingEndedInside, .trackingEndedOutside, .trackingCancelled], que agrega una pulsación que termina fuera del botón.19 trackingEndedOutside no es nuevo: la documentación de Apple lo lista en macOS 11.0, y el SDK de macOS 27.0 lo declara como NSControlEventTrackingEndedOutside = 1 << 7 en NSControl.h (análisis del autor).67 El artículo también señala que los métodos de acción deben exponerse a Objective-C con @objc, y que si solo necesitas la activación, primaryActionTriggered se dispara “regardless of whether someone used a mouse, trackpad, or touch to produce it.” (sin importar si se produjo con un mouse, un trackpad o el toque).1
Las vistas contenedoras tienen la misma trampa: “If you start a drag from mouseDragged(with:) in an NSTableView, NSCollectionView, NSOutlineView, or NSBrowser, AppKit falls back to a tracking loop for the interaction” (si inicias un arrastre desde mouseDragged(with:) en esas vistas, AppKit vuelve a un bucle de seguimiento para la interacción), y la salida son los métodos de arrastre de la fuente de datos y del delegado, tableView(_:pasteboardWriterForRow:) en el caso de una tabla.1
Para las dos sobrescrituras de mouseDown(with:) más comunes, el artículo da pares de antes y después. “Most mouseDown(with:) overrides do one of two things: mark an item as selected when someone clicks it, or show a menu when someone Control-clicks.” (la mayoría de esas sobrescrituras hacen una de dos cosas: marcar un elemento como seleccionado al hacer clic o mostrar un menú con Control-clic).1 Para la selección, observa en su lugar isSelected en el elemento de la vista de colección o en la vista de fila de la tabla, con la advertencia de que el observador “also runs when your code changes the selection programmatically” (también se ejecuta cuando tu código cambia la selección por programación); para el menú, sobrescribe menu(for:) y deja que AppKit lo presente, o asigna menu o defaultMenu cuando el menú no depende del clic.1
Un reconocedor solo táctil junto a un bucle que conservas
La charla recomendaba un reconocedor de desplazamiento solo táctil junto a un bucle de seguimiento existente como la ruta para una versión menor. El artículo documenta la misma receta bajo “Add touch support to your custom tracking loop”, con las funciones compartidas de inicio, continuación, fin y cancelación, y luego dice lo que hace cada propiedad con una precisión para la que la charla no tenía espacio:1
let touchRecognizer = NSPanGestureRecognizer(target: self, action: #selector(handleTouchPan))
touchRecognizer.buttonMask = 0
touchRecognizer.allowedTouchTypes = .direct
addGestureRecognizer(touchRecognizer)
“setting buttonMask to 0 means the gesture recognizer only recognizes touches, not mouse or trackpad input, so your tracking loop keeps handling the mouse and trackpad exactly as before.” (con buttonMask en 0, el reconocedor solo reconoce toques, no la entrada del mouse ni del trackpad, así que tu bucle de seguimiento sigue manejando el mouse y el trackpad exactamente como antes).1 Si dejas la máscara en su valor por defecto, el reconocedor “also responds to the primary mouse button. A mouse drag then begins the gesture recognizer, and AppKit doesn’t call your mouseDown(with:) override, so the tracking loop doesn’t run. A click that doesn’t move still reaches your tracking loop.” (también responde al botón principal del mouse; un arrastre del mouse inicia entonces el reconocedor, AppKit no llama a tu sobrescritura de mouseDown(with:) y el bucle no se ejecuta; un clic sin movimiento sí llega al bucle).1 Así que la máscara no es un detalle opcional; sin ella, el bucle deja de recibir arrastres sin avisar. La línea de allowedTouchTypes es una precaución adicional: “Direct touches are already the default for allowedTouchTypes, and setting it to .direct states that intent explicitly.” (los toques directos ya son el valor por defecto de allowedTouchTypes, y asignar .direct deja explícita esa intención).1 La valoración de Apple sobre el enfoque: “This is often the fastest, lowest-risk way to add touch support to code with a tracking loop you’d rather not rewrite all at once.” (suele ser la forma más rápida y de menor riesgo de agregar soporte táctil a código con un bucle de seguimiento que prefieres no reescribir de una vez).1
Si en cambio reemplazas el bucle, el ejemplo de desplazamiento del artículo devuelve la etiqueta a su origen inicial en .cancelled, y un bucle que iniciaba una sesión de arrastre pasa el reconocedor a beginDraggingSession(items:gesture:source:) en lugar del evento.1
Filtrar con los dos métodos delegados
La sección más nueva respecto de las charlas es “Filter gesture recognizer candidates”. Los reconocedores se reúnen por hit-testing, no por la cadena de respondedores: “Gesture recognizers don’t use the responder chain.”1 Cuando AppKit los reúne, “not every recognizer it finds is actually applicable to the interaction” (no todos los reconocedores que encuentra se aplican de verdad a la interacción), y la app descarta los que no se aplican mediante dos métodos delegados: gestureRecognizer(_:shouldAttemptToRecognizeWith:) “to inspect the initiating NSEvent for pointer-based input” (para inspeccionar el NSEvent inicial en la entrada de puntero), y gestureRecognizer(_:shouldReceive:) “to inspect each new NSTouch for touch input.” (para inspeccionar cada NSTouch nuevo en la entrada táctil).1 El sentido de ambos: “Either method gives you the initial input, something the recognizer doesn’t otherwise expose, and lets you stop the recognizer from handling that event stream or touch.” (cualquiera de los dos te da la entrada inicial, algo que el reconocedor no expone de otro modo, y te permite impedir que el reconocedor maneje ese flujo de eventos o ese toque).1 Y la división es estricta: “Direct touches don’t call this method. To filter them, implement gestureRecognizer(:shouldReceive:) instead, as shown above.” (los toques directos no llaman a este método; para filtrarlos, implementa gestureRecognizer(:shouldReceive:) en su lugar, como se muestra arriba).1 Si filtras solo con el método de eventos, ese delegado deja sin filtrar los toques directos.
Los dos ejemplos son patrones que espero ver copiados. Un tirador de redimensionar: se adjunta el reconocedor de redimensionado a toda la vista y, en ambos métodos delegados, se devuelve si la ubicación está dentro del marco del tirador, devolviendo true “only to give the gesture recognizer a chance to attempt recognition, not to guarantee it succeeds.” (solo para darle al reconocedor la oportunidad de intentar el reconocimiento, no para garantizar que lo logre).1 Una línea de tiempo de clips: un único reconocedor en la línea de tiempo y, en el método de eventos, se hace hit-testing del clip, se guarda para el método de acción y se devuelve false cuando no hay ninguno.1 La advertencia que acompaña a los objetivos pequeños: “If a custom control expects a touch to land inside its visible area, update it to handle touches that land outside that area.” (si un control propio espera que el toque caiga dentro de su área visible, actualízalo para manejar toques que caen fuera de ella).1
La prioridad entre los que sobreviven es un requisito de fallo, y el artículo repite la advertencia del segundo artículo, “Use this sparingly, because the waiting recognizer responds more slowly” (úsalo con moderación, porque el reconocedor que espera responde más lento), con el ejemplo de que “making a single click wait for a double click to fail makes clicks feel sluggish.” (hacer que un clic simple espere a que falle un doble clic hace que los clics se sientan lentos).1
currentEvent, hover y deslizamientos
“Handle touch input” repite en forma de documentación la frase que ordenaba la charla: AppKit “doesn’t deliver direct touches (NSTouch) through the responder chain or through event monitors” (no entrega toques directos por la cadena de respondedores ni por los monitores de eventos), y “Gesture recognizers are the only way to respond to multi-finger gestures or to receive individual touches from a Mac with a touch display or a Sidecar-connected iPad.” (los reconocedores de gestos son la única forma de responder a gestos de varios dedos o de recibir toques individuales de una Mac con pantalla táctil o de un iPad conectado por Sidecar).1 La página general de entrada de AppKit dice lo mismo desde el lado del respondedor: “The responder chain receives touch events only from indirect touches on a trackpad, if a view asks for them. It doesn’t receive touch events from direct touches on a Mac with a touch display.” (la cadena de respondedores solo recibe eventos táctiles de toques indirectos en un trackpad, si una vista los pide, y no de toques directos en una Mac con pantalla táctil).6
El consejo sobre las comprobaciones de capacidad es evitarlas: “In most cases, instead of checking whether a device supports touch, write your event-handling code to work the same way regardless of input source, and decide on alternate presentations based on the interaction type.” (en la mayoría de los casos, en vez de comprobar si un dispositivo admite el toque, escribe el manejo de eventos para que funcione igual con cualquier fuente de entrada y decide las presentaciones alternativas según el tipo de interacción).1 El único ejemplo que se da para touchCapabilities es “capturing a signature on a touch display.” (capturar una firma en una pantalla táctil).1
currentEvent tiene su propia sección. Como las interacciones con controles pasan por reconocedores, “currentEvent no longer reliably returns the event that initiated an action” (currentEvent ya no devuelve de forma fiable el evento que inició una acción), y el par de antes y después reemplaza los modificadores del evento por el NSEvent.modifierFlags a nivel de clase, con la advertencia de que “These report the state at the time you query them, not at the time of the original event. A touch doesn’t register in pressedMouseButtons.” (estos informan el estado en el momento en que los consultas, no en el del evento original; un toque no se registra en pressedMouseButtons).1 Lo que no cambia: “Keyboard events and scroll-wheel events behave the same on every input source: AppKit delivers them through the responder chain. Hover tracking, which relies on mouseMoved(with:) and NSTrackingArea, applies to pointer input only, because a touch display has no hover.” (los eventos de teclado y de rueda de desplazamiento se comportan igual con cualquier fuente de entrada: AppKit los entrega por la cadena de respondedores; el seguimiento del hover, que depende de mouseMoved(with:) y NSTrackingArea, solo se aplica a la entrada de puntero, porque una pantalla táctil no tiene hover).1
Los deslizamientos no se traducen: “AppKit doesn’t translate touch swipes for you.”1 Si usas trackSwipeEvent(options:dampenAmountThresholdMin:max:usingHandler:), implementa los tuyos y, para la navegación hacia atrás y hacia adelante, comprueba primero NSEvent.isTouchSwipeNavigationEnabled, “because the person can turn off swipe navigation in System Settings.” (porque la persona puede desactivar la navegación por deslizamiento en Configuración del Sistema).1 Esa propiedad es nueva: la documentación de Apple la lista en macOS 27.0, y NSEvent.h en el SDK de macOS 27.0 la declara en la línea 496 como API_AVAILABLE(macos(27)) (análisis del autor).67
Reconocedores propios: el contrato, el tiempo límite y dos ajustes de depuración
Las pautas del artículo para una subclase propia de NSGestureRecognizer son un contrato en siete viñetas: manejar la cancelación en mouseCancelled(with:) y touchesCancelled(with:); en los métodos de eventos, solo actualizar el estado privado de seguimiento y asignar state; responder en el método de acción, no en el callback del evento; guardar en caché el NSEvent o el NSTouch de origen para el método de acción; “Override reset() to clear your cached state, so it doesn’t leak from one gesture into the next” (sobrescribe reset() para limpiar el estado en caché y que no pase de un gesto al siguiente); “Always end an active gesture recognizer by transitioning to NSGestureRecognizer.State.ended, NSGestureRecognizer.State.failed, or NSGestureRecognizer.State.cancelled. Otherwise, other recognizers can’t activate” (termina siempre un reconocedor activo pasando a ended, failed o cancelled; si no, los demás reconocedores no pueden activarse); y sobrescribir location(in:), porque “If you skip this override and build your app with the macOS 27 SDK and later, AppKit returns NSZeroPoint and logs a console error.” (si omites esta sobrescritura y compilas con el SDK de macOS 27 o posterior, AppKit devuelve NSZeroPoint y registra un error en la consola).1 El ejemplo DragTrackingRecognizer que sigue es un andamiaje: los cuerpos de sus sobrescrituras de mouse-down, dragged y up son comentarios que te indican llamar a un método privado track(_:event:state:), el de la sobrescritura de cancelación indica asignar .cancelled, y el método de acción vuelve a leer la ubicación y el evento guardados.1
El segundo artículo es donde se cuantifican las consecuencias de romper el contrato. “A gesture recognizer is stuck if it hasn’t reached a terminal state within 2 seconds after the person stops interacting with your app, such as when they release the mouse button or lift their fingers.” (un reconocedor está atascado si no llegó a un estado terminal 2 segundos después de que la persona deja de interactuar con la app, por ejemplo al soltar el botón del mouse o levantar los dedos).2 Con las vistas exclusivas, “A stuck gesture recognizer keeps holding its exclusive view” (un reconocedor atascado sigue reteniendo su vista exclusiva), lo que bloquea la reunión de reconocedores fuera de su jerarquía, y el remedio del sistema es automático: “To keep a single stuck gesture recognizer from blocking your app indefinitely, the system automatically transitions it to the NSGestureRecognizer.State.cancelled state, allowing gesture gathering to resume normally.” (para que un solo reconocedor atascado no bloquee la app indefinidamente, el sistema lo pasa automáticamente al estado cancelled y la reunión de gestos se reanuda con normalidad).2 Ese es el mecanismo detrás de la cuarta fila de la tabla, y significa que un reconocedor defectuoso en una app publicada bloquea la reunión de gestos fuera de su jerarquía exclusiva, con el resto de la app aparentemente sin responder, hasta la cancelación automática dos segundos después de que la persona deja de interactuar, y el target de la acción sigue siendo responsable de manejar la cancelación.
Dos ajustes de UserDefaults hacen que estos problemas provoquen un fallo con un stack trace mientras desarrollas, uno para una sobrescritura que falta y otro en lugar de la cancelación silenciosa. “Set the NSGestureRecognizerCrashOnMissingOverrides key in UserDefaults to YES”, o pasa -NSGestureRecognizerCrashOnMissingOverrides YES como argumento de lanzamiento, para que la app falle cuando falta un método de eventos que deberías haber sobrescrito; y asigna a NSCrashOnStuckGestureTimeout “to a small number of seconds, such as 2” (un número pequeño de segundos, como 2), o pasa -NSCrashOnStuckGestureTimeout 2, para que falle en lugar de cancelar un reconocedor atascado.2 El primero no lo detecta todo: “doesn’t detect a gesture recognizer that correctly transitions to NSGestureRecognizer.State.cancelled and sends its action, if the action’s target doesn’t handle the canceled state.” (no detecta un reconocedor que pasa correctamente a cancelled y envía su acción, si el target de la acción no maneja el estado cancelado).2 La advertencia que sigue es de las que van en una lista de verificación: “Comment out or remove NSGestureRecognizerCrashOnMissingOverrides and NSCrashOnStuckGestureTimeout before shipping your app” (comenta o elimina ambos antes de publicar la app), y “Changes to these user defaults or launch arguments take effect only after you rebuild or relaunch your app.” (los cambios en estos ajustes o argumentos de lanzamiento solo surten efecto después de recompilar o relanzar la app).2 Ninguno de los dos ajustes aparece en las charlas ni en la nota técnica de junio.
El mismo artículo explica la diferencia entre los dos estados terminales que no son de éxito: .failed es para un reconocedor que nunca coincidió, y “If a gesture recognizer sets this after it already delivered an action, the system automatically converts it to NSGestureRecognizer.State.cancelled instead.” (si un reconocedor lo asigna después de haber entregado ya una acción, el sistema lo convierte automáticamente en cancelled).2 También nombra una causa de cancelación por el sistema además del ejemplo de Exposé de la charla: “when the same touches begin a system-level gesture like smartMagnify(with:), the system takes over those touches for its own use.” (cuando los mismos toques inician un gesto a nivel del sistema como smartMagnify(with:), el sistema se queda con esos toques para su propio uso).2
Tres datos menores de la misma página. Los reconocedores pueden retrasar los eventos de los respondedores: “If you set delaysPrimaryMouseButtonEvents to true, the system stores the relevant events until the gesture recognizer fails, then delivers them to the appropriate responders. If the gesture recognizer reaches the NSGestureRecognizer.State.ended state, the system never delivers the events.” (con delaysPrimaryMouseButtonEvents en true, el sistema guarda los eventos pertinentes hasta que el reconocedor falla y luego los entrega a los respondedores correspondientes; si el reconocedor llega a ended, nunca los entrega).2 Y los métodos táctiles propios del respondedor son solo para el trackpad: “These methods don’t have an equivalent delay mechanism, and they apply only to indirect touches from a trackpad.” (estos métodos no tienen un mecanismo de retraso equivalente y solo se aplican a toques indirectos de un trackpad).2 Un view controller no puede tener un reconocedor: “You can’t attach a gesture recognizer to a view controller directly”, así que agrégalo a la vista del controlador, con el controlador como target y delegado.2
Vistas exclusivas: activadas por defecto, desactivadas en las plantillas de AppKit de Xcode
El post de la tarde explicaba la exclusividad a partir de la charla y de la nota técnica de junio. Los dos artículos agregan el valor por defecto, el mecanismo y un dato de Xcode.
El mecanismo, nuevo en macOS 27: “In macOS 27 and later, the initial hit-tested view for a mouse click or touch event becomes an exclusive view for gathering gesture recognizers” (en macOS 27 y posteriores, la primera vista que alcanza el hit-testing en un clic o un toque se convierte en vista exclusiva para reunir reconocedores de gestos), y “This behavior simulates the presence of a single mouse pointer, even when a person touches a Mac with a touch display with several fingers at once.” (este comportamiento simula la presencia de un solo puntero de mouse, aunque la persona toque una Mac con pantalla táctil con varios dedos a la vez).2 El valor por defecto: “Exclusive views are on by default”, y “If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES.” (si el Info.plist de la app no incluye la clave, el sistema la trata como YES).2 La página de la propia clave coincide, “The default value is YES”, y plantea la exclusión de forma estrecha: “Set this key to NO to allow gesture recognizers in multiple views to activate simultaneously. Do this only if your app intentionally needs concurrent gesture recognition across different views.” (asígnale NO para que reconocedores de varias vistas puedan activarse a la vez, y hazlo solo si la app necesita a propósito reconocimiento simultáneo entre vistas distintas).6 El ejemplo del sistema de exclusión por vista es el Color Panel: “AppKit uses this same technique in the standard Color Panel”, donde varios sliders se mueven a la vez.2
El dato de Xcode, que aparece en ambos artículos: “Xcode adds the NSViewGestureRecognizerIsExclusive key with a value of NO to new macOS app projects, so those apps don’t use exclusive views unless you change the value to YES.” (Xcode agrega la clave con el valor NO a los proyectos nuevos de apps de macOS, así que esas apps no usan vistas exclusivas a menos que cambies el valor a YES).1 Leí las plantillas de proyecto en esta Mac para ver qué proyectos abarca (análisis del autor). En Xcode 27.1 (27A9275) y en la beta de Xcode 27.2 (27B5019j), la plantilla macOS App hereda de una unidad “Cocoa App Base” cuya opción de ciclo de vida de app tiene dos valores, SwiftUI y Cocoa, con SwiftUI por defecto; la clave INFOPLIST_KEY_NSViewGestureRecognizerIsExclusive con el valor NO está solo dentro de la unidad Cocoa, así que Xcode la escribe, como un build setting que termina en el Info.plist generado, cuando el proyecto usa el ciclo de vida del app delegate de AppKit y no cuando usa el ciclo de vida de SwiftUI.7 La opción de ciclo de vida no lleva nombre visible en las plantillas; la que sí lo lleva es Interface: (SwiftUI, Storyboard o XIB, con SwiftUI por defecto), y las unidades Storyboard y XIB requieren el ciclo de vida de AppKit, así que un proyecto Storyboard o XIB recibe la clave, y un proyecto con interfaz SwiftUI la recibe solo cuando su ciclo de vida es el app delegate de AppKit.7 La plantilla Document App desciende de la misma base, así que, con la misma condición, hereda ese build setting y además escribe la clave como entrada de Info.plist con el valor false; la plantilla Game de macOS fuerza el ciclo de vida Cocoa, así que un juego nuevo de macOS empieza sin exclusividad.7 En esta Mac no hay ningún Xcode anterior a 27.1, así que no puedo decir cuándo las plantillas incorporaron la clave. La lectura práctica: un proyecto de AppKit creado con estas plantillas empieza sin exclusividad; un proyecto de SwiftUI, y cualquier app existente cuyo Info.plist no tenga la clave, es exclusivo. La nota de migración del segundo artículo se deriva de eso: si tu app asignó NO a la clave, “also set the view’s exclusiveGestureBehavior to NSView.ExclusiveGestureBehavior.exclusive” (asigna también exclusive al exclusiveGestureBehavior de la vista) cuando reemplaces un bucle de seguimiento, porque “This prevents simultaneous gesture recognition with unrelated views, which the loop previously prevented implicitly.” (esto impide el reconocimiento simultáneo con vistas no relacionadas, algo que antes el bucle impedía de forma implícita).2
El primer artículo desenreda los tres ajustes que tocan la exclusividad y el desplazamiento, con la frase “These settings don’t override each other.” (estos ajustes no se anulan entre sí).1 exclusiveGestureBehavior y la clave del plist deciden qué reconocedores reúne AppKit, y “The view’s setting applies to that view and its subviews, and takes precedence over the app-wide key.” (el ajuste de la vista se aplica a esa vista y a sus subvistas, y tiene prioridad sobre la clave de toda la app).1 isCancellableByScrollGesture es por reconocedor e independiente: “It lets a scroll gesture cancel that gesture recognizer even if it has already begun.” (permite que un gesto de desplazamiento cancele ese reconocedor aunque ya haya empezado).1 scrollGestureForRelationships es para los reconocedores que compiten con los de la propia vista de desplazamiento.1 El escenario que el artículo pide vigilar es un botón dentro de una vista de desplazamiento: asigna true a isCancellableByScrollGesture en su reconocedor para que un arrastre que empieza en el botón desplace en lugar de hacer clic.1
Los motores de texto reciben una herramienta más. NSTextSelectionManager “creates its gesture recognizers itself, so you don’t have a reference to them” (crea sus reconocedores por su cuenta, así que no tienes referencia a ellos), de modo que un reconocedor propio que deba esperarlos devuelve true desde gestureRecognizer(_:shouldRequireFailureOf:) cuando el gesturesForFailureRequirements del administrador contiene el otro reconocedor.1 Esa propiedad figura en macOS 27.0 y está declarada en NSTextSelectionManager.h, en la línea 59 del SDK de macOS 27.0 (análisis del autor).67
La emulación, en cinco viñetas
El post de la tarde citaba las cuatro reglas de emulación de la nota técnica de junio para los frameworks de UI que no son de AppKit. La lista del artículo tiene cinco, y la nueva es el arrastre.18
- “Tapping emulates a mouse-down followed immediately by a mouse-up.” (un toque emula un mouse-down seguido de inmediato por un mouse-up)
- “Touching and then immediately panning emulates a trackpad scroll, even if the UI doesn’t otherwise handle Multi-Touch input directly.” (tocar y desplazar de inmediato emula un desplazamiento del trackpad, aunque la UI no maneje directamente la entrada Multi-Touch)
- “Touching, briefly holding, and then moving emulates mouseDown(with:), followed by mouseDragged(with:) as it moves, and mouseUp(with:) on lift.” (tocar, mantener brevemente y luego mover emula mouseDown(with:), seguido de mouseDragged(with:) mientras se mueve, y mouseUp(with:) al levantar el dedo)
- “Touching and holding without movement emulates rightMouseDown(with:), and rightMouseUp(with:) on lift.” (tocar y mantener sin mover emula rightMouseDown(with:), y rightMouseUp(with:) al levantar el dedo)
- “Pinching or rotating with two fingers emulates the trackpad magnify and rotate gestures.” (pellizcar o rotar con dos dedos emula los gestos de ampliar y rotar del trackpad)
La tercera viñeta es la forma escrita del “drag with a small hold delay” (arrastre con una breve espera) de la charla (11:31): un toque que se mueve de inmediato desplaza, y un toque que primero se mantiene arrastra.9 La lista de junio tenía un toque, un desplazamiento inmediato, una pulsación larga y un pellizco o rotación con dos dedos, y ninguna viñeta de arrastre.8 El límite son dos dedos: “This emulation covers interactions with one or two fingers. If another finger touches down during a pinch or rotation, the emulation ignores it and keeps tracking the original two fingers.” (esta emulación cubre interacciones de uno o dos dedos; si otro dedo toca durante un pellizco o una rotación, la emulación lo ignora y sigue los dos dedos originales).1 Tres dedos o más necesitan reconocedores, y NSIsTouchNative en YES desactiva la emulación adicional una vez que el framework maneja el toque por su cuenta.1
Juegos: TN3216
La nota técnica es para juegos: “This guidance focuses on configurations common in games, such as an app with a single view backed by a CAMetalLayer.” (esta guía se centra en configuraciones comunes en los juegos, como una app con una sola vista respaldada por una CAMetalLayer).3 Su planteamiento de diseño es más estricto que el de las HIG: “Touch extends the Mac experience rather than replacing other input” (el toque amplía la experiencia de la Mac en lugar de reemplazar otras entradas), y “Touch works best for brief interactions that complement these devices” (el toque funciona mejor en interacciones breves que complementan esos dispositivos), con la instrucción de “Design touch interactions for brief moments that the player can complete with one hand, while the other hand stays on the keyboard, mouse, or controller” (diseña interacciones táctiles para momentos breves que el jugador pueda completar con una mano mientras la otra sigue en el teclado, el mouse o el control), en interacciones “such as inventory management, map zoom and scroll, or the settings menu.” (como el manejo del inventario, el zoom y el desplazamiento del mapa o el menú de configuración).3
La historia de compatibilidad es la misma emulación con un giro para juegos. “AppKit applies game-specific emulation to apps in a games category, and can apply emulation to an individual view, such as one that hosts a third-party UI framework.” (AppKit aplica una emulación específica para juegos a las apps de la categoría de juegos, y puede aplicar emulación a una vista concreta, como una que aloja un framework de UI de terceros).3 La categoría se lee del Info.plist: si LSApplicationCategoryType es public.app-category.games o una subcategoría de juegos, “the system applies game-specific mouse emulation.” (el sistema aplica la emulación de mouse específica para juegos).3 El problema que resuelve es uno que un mouse nunca tiene: “A mouse moves continuously from one position to the next, but a player can tap or start a drag at two distant locations in succession” (un mouse se mueve de forma continua de una posición a la siguiente, pero un jugador puede tocar o empezar un arrastre en dos lugares distantes, uno tras otro), así que un juego que suma deltas se desvía, y “For games, AppKit sends a mouseMoved event to the new location before each mouseDown(with:), so your game can refresh its cached mouse position.” (en los juegos, AppKit envía un evento mouseMoved a la nueva ubicación antes de cada mouseDown(with:), para que el juego actualice la posición del mouse que tiene guardada).3
Luego la nota técnica lista en orden los eventos emulados. Para un toque: mouseMoved(with:) “from the last mouse position to the tap location” (desde la última posición del mouse hasta el lugar del toque), luego mouseDown(with:) y luego mouseUp(with:). Para un arrastre: mouseMoved(with:) hasta donde empieza el arrastre, mouseDown(with:), mouseDragged(with:) “repeatedly while the touch continues” (repetidamente mientras sigue el toque) y mouseUp(with:).3 Después, una regla en un recuadro “Important”: “Don’t intercept events by overriding sendEvent(:) or pulling them with nextEvent(matching:until:inMode:dequeue:). Doing so can interfere with gesture recognizers that respond to a physical mouse.” (no interceptes eventos sobrescribiendo sendEvent(:) ni extrayéndolos con nextEvent(matching:until:inMode:dequeue:), porque puede interferir con los reconocedores que responden a un mouse físico).3
La parte que no tiene equivalente en las charlas es GCMouse. “By default, the system presents each touch surface, whether a Mac with a touch display or a Sidecar-connected iPad, as an additional GCMouse that emulates mouse input from touches” (por defecto, el sistema presenta cada superficie táctil, sea una Mac con pantalla táctil o un iPad conectado por Sidecar, como un GCMouse adicional que emula la entrada del mouse a partir de los toques): un dedo que toca presiona el botón izquierdo, el movimiento llama al handler de movimiento del mouse con un delta y levantar el dedo lo suelta.3 Sus límites: “The emulated GCMouse ignores additional fingers, and it doesn’t emulate other buttons, scrolling, or pinch input” (el GCMouse emulado ignora los dedos adicionales y no emula otros botones, el desplazamiento ni el pellizco), y como los toques caen en lugares inconexos, “Don’t use them to track an absolute pointer position.” (no los uses para seguir una posición absoluta del puntero).3
NSIsTouchNative hace más por un juego que por una app de AppKit. Con el valor YES, “AppKit’s emulation will stick to a generic emulation - ignoring the app’s category or whether it uses a third-party UI framework - and disable GCMouse emulation” (la emulación de AppKit se limita a una emulación genérica, sin tener en cuenta la categoría de la app ni si usa un framework de UI de terceros, y desactiva la emulación de GCMouse), así que la superficie táctil ya no aparece como un GCMouse y “Your game receives touch through gesture recognizers instead of emulated mouse events.” (el juego recibe el toque por reconocedores de gestos en lugar de eventos de mouse emulados).3 Queda un residuo: “AppKit still emulates mouse events for any NSView that handles them, for example by overriding mouseDown:.” (AppKit sigue emulando eventos de mouse para cualquier NSView que los maneje, por ejemplo sobrescribiendo mouseDown:).3 La página de la propia clave dice que la mayoría de las apps de AppKit no la necesitan y que está “primarily intended for apps that embed a non-AppKit rendering or input framework and implement their own touch-handling pipeline” (pensada principalmente para apps que integran un framework de renderizado o de entrada ajeno a AppKit e implementan su propia canalización de manejo de toques); un motor de juegos es ese caso.6
La sección de implementación es un patrón de delegado en Swift y Objective-C: un reconocedor de desplazamiento y uno de toque con buttonMask en 0 para que ignoren un mouse físico, un reconocedor de ampliación, shouldRecognizeSimultaneouslyWith que devuelve true solo para el par de desplazamiento y pellizco, y shouldRequireFailureOf, que hace que el toque espere a los dos.3 Una nota confirma el valor por defecto que afirma el primer artículo: “The gesture recognizers must include direct in allowedTouchTypes. On macOS 27 and later, this is the default.” (los reconocedores deben incluir direct en allowedTouchTypes; en macOS 27 y posteriores, es el valor por defecto).3 El consejo de enrutamiento tiene forma de motor: “Install the gesture recognizers once in your app and leave them enabled” (instala los reconocedores una vez en la app y déjalos activados), convierte los callbacks en eventos de entrada del motor y “Let the active input handler decide if and how to react to different gestures” (deja que el manejador de entrada activo decida si reacciona a los distintos gestos y cómo), con un handler de desplazamiento que convierte puntos a píxeles con el factor de escala del backing y voltea el eje y para un motor cuyo origen está arriba a la izquierda.3 Los controles estándar siguen funcionando después del cambio, “as long as you don’t attach your own direct-touch gesture recognizers to them.” (siempre que no les adjuntes tus propios reconocedores de toque directo).3
La lista de verificación final tiene dos mitades. Para un juego que se apoya en la emulación: lee la ubicación de cada evento, y no sobrescribas sendEvent: ni extraigas eventos de nextEventMatchingMask:. Para un juego que maneja el toque: configura NSIsTouchNative, usa reconocedores de toque directo con buttonMask en 0, agrega el toque donde los jugadores lo esperan y mantenlo para una sola mano, “Continue to read GCMouse for the physical mouse. With NSIsTouchNative set, touch no longer appears as an emulated GCMouse” (sigue leyendo GCMouse para el mouse físico; con NSIsTouchNative configurada, el toque ya no aparece como un GCMouse emulado), y usa controles estándar donde sea posible.3
Las HIG, con fecha del 28 de octubre
El post de la tarde decía que las cuatro páginas de las HIG que la página de aterrizaje pensaba enlazar no tenían ninguna guía sobre el toque en la pantalla de la Mac. Tres de ellas la tienen ahora, cada una con una fila en el registro de cambios fechada el 28 de octubre de 2026; la página de Live Activities no cambió.4
Designing for macOS (“Added guidance for touch input.”) reescribe tres líneas de su descripción de la plataforma y agrega una buena práctica. Ergonomía: “On a Mac with a touch display, people reach forward to touch the screen, so they tend to use touch for brief, intentional interactions rather than sustained work.” (en una Mac con pantalla táctil, la gente estira el brazo para tocar la pantalla, así que tiende a usar el toque para interacciones breves e intencionales, no para trabajo sostenido). Las entradas ahora empiezan por los teclados y los dispositivos señaladores y listan “Gestures on a trackpad or a Mac with a touch display” entre las demás. La nueva buena práctica es toda la charla de diseño en una frase: “Support standard gestures on trackpads and Mac devices with a touch display. Use larger tap targets and controls that expand on touch to make touch interaction easier, while keeping control size and density the same to stay optimized for keyboard and pointing device input.” (admite los gestos estándar en trackpads y en Mac con pantalla táctil; usa áreas de toque más grandes y controles que se expanden al tocarlos para facilitar la interacción táctil, manteniendo el mismo tamaño y la misma densidad de los controles para seguir optimizado para el teclado y los dispositivos señaladores).4
Gestures (“Added guidance for gestures and touch input in macOS.”) amplía su sección de macOS, que ahora enlaza los dos artículos de AppKit como guía para desarrolladores. Su primera frase fija el rango: “On a Mac with a touch display, or on an iPad connected with Sidecar, people can also interact directly using touch gestures as a secondary input mode.” (en una Mac con pantalla táctil, o en un iPad conectado por Sidecar, la gente también puede interactuar directamente con gestos táctiles como modo de entrada secundario).4 Luego vienen seis reglas, cada una con un encabezado en negrita:
- “Keep the layout and size of interface elements the same when you support touch.” (mantén igual el diseño y el tamaño de los elementos de la interfaz al admitir el toque). La razón: “In macOS, touch is secondary to keyboard and pointing device input” (en macOS, el toque es secundario frente al teclado y los dispositivos señaladores), y la gente alterna entre entradas a menudo. El contraste con el iPhone Duo, donde la pantalla interna cambia de clases de tamaño, es justamente la idea: en la Mac cambia la entrada y no cambia el diseño.
- “Make tap targets bigger, not the controls themselves” (haz más grandes las áreas de toque, no los controles), “enlarging their tap targets to anticipate that interaction may start slightly outside of their visual bounds” (ampliando sus áreas de toque para prever que la interacción empiece un poco fuera de sus límites visuales), y “Where possible, use standard framework controls, which the system configures with an optimal tap target size automatically.” (donde sea posible, usa los controles estándar del framework, que el sistema configura automáticamente con un tamaño óptimo de área de toque).
- “Consider providing expandable controls” (considera ofrecer controles expandibles), una variante temporal de tamaño táctil que aparece al tocar, se mantiene mientras se presiona y permanece “for a short time after they lift their finger to keep the control accessible to touch” (un rato después de levantar el dedo, para que el control siga accesible al toque); “When using standard system controls, steppers, sliders, and dials gain this behavior automatically.” (con los controles estándar del sistema, los steppers, sliders y diales obtienen este comportamiento automáticamente).
- “Don’t use touch as the only way to access controls or features” (no uses el toque como la única forma de acceder a controles o funciones), y lo inverso: “aim to provide touch equivalents for actions otherwise performed exclusively with a pointing device, like using a keyboard modifier to shift-drag or displaying a menu when the pointer moves over an element.” (procura ofrecer equivalentes táctiles para las acciones que de otro modo solo se hacen con un dispositivo señalador, como usar una tecla modificadora para arrastrar con Shift o mostrar un menú cuando el puntero pasa sobre un elemento).
- “Where possible, align with gestures people already know” (donde sea posible, alinéate con los gestos que la gente ya conoce), los de tu app de iPhone o iPad o los del trackpad, y “Test to ensure they don’t conflict with macOS system gestures.” (prueba que no choquen con los gestos del sistema de macOS).
- “Make sure custom gestures work for both touch and trackpad” (asegúrate de que los gestos propios funcionen con el toque y con el trackpad), usando “modifier keys where needed to help the gesture work for all touch inputs.” (teclas modificadoras donde haga falta para que el gesto funcione con todas las entradas táctiles).4
La página no da la cifra de 36 puntos; ese número está en las dos charlas (“Touch on macOS uses a touch target of 36 points”, 9:17 en la charla de diseño, y la ampliación del hit rect en la charla de AppKit a “a minimum of 36 points on each axis”, 5:53).9 Agrega una tabla, “macOS trackpad and touch display gestures”, que empareja cada acción con su forma en el trackpad y en la pantalla táctil: seleccionar es un clic principal o un toque; abrir es un doble clic o un doble toque; desplazar es con dos dedos en el trackpad y con uno en la pantalla; ampliar y rotar son el mismo pellizco y el mismo giro con dos dedos en ambos; un menú contextual es un clic secundario o tocar y mantener; un arrastre es hacer clic y arrastrar, o “Touch and hold, then drag.” (tocar y mantener, luego arrastrar).4 En la tabla multiplataforma, “Touch (or pinch) and hold” ahora lista macOS, y la fila del doble toque agrega “open an item on a Mac with a touch display.” (abrir un elemento en una Mac con pantalla táctil).4
Pointing devices (“Added guidance for revealing hidden controls with touch on Mac, and updated guidance and artwork for iPadOS and macOS.”) es la página con las reglas más concretas, bajo un encabezado nuevo, “Touch support for hidden controls”. La premisa: “Some Mac apps reveal certain controls only when the pointer moves over them. These controls need another way to appear when someone uses touch on a Mac that supports it.” (algunas apps de Mac revelan ciertos controles solo cuando el puntero pasa sobre ellos; esos controles necesitan otra forma de aparecer cuando alguien usa el toque en una Mac que lo admite).4 Las reglas:
- “Decide which hidden controls need touch support. Reserve touch support for essential controls.” (decide qué controles ocultos necesitan soporte táctil y resérvalo para los esenciales). Los tooltips no califican, ni nada que el toque ya haga de otra manera: “someone can swipe to scroll a carousel, so there’s no need to reveal the navigation buttons that exist for mice without a scroll wheel.” (se puede deslizar para desplazar un carrusel, así que no hace falta revelar los botones de navegación que existen para los mouse sin rueda). Y: “Avoid keeping hidden controls visible all the time just to accommodate touch.” (evita dejar visibles todo el tiempo los controles ocultos solo para acomodar el toque).
- “Reveal a hidden control when someone taps its general location on a touch display.” (revela un control oculto cuando alguien toca su zona general en una pantalla táctil). Solo los relevantes, y “Hide a control again when the person finishes with it, or after a few seconds if they don’t use it.” (vuelve a ocultarlo cuando la persona termina con él, o tras unos segundos si no lo usa).
- “Retain the current selection when revealing a hidden control” (conserva la selección actual al revelar un control oculto), porque el control puede actuar sobre la selección.
- “Make essential hidden controls available in a context menu” (ofrece los controles ocultos esenciales en un menú contextual), que el toque abre al tocar y mantener.
- “In a sidebar, reveal hidden controls when someone taps the corresponding section header or row.” (en una barra lateral, revela los controles ocultos cuando alguien toca el encabezado de sección o la fila correspondiente). El ejemplo es el Finder: “tapping the Favorites header in the Finder sidebar collapses the section and reveals a disclosure button.” (tocar el encabezado Favoritos en la barra lateral del Finder contrae la sección y revela un botón desplegable). Los controles revelados permanecen hasta que el toque sale de la barra lateral o se usa un dispositivo señalador, y un toque en la zona de un control oculto lo revela sin activarlo: “The control acts only when they tap it again.” (el control solo actúa cuando se vuelve a tocar).
- “In a tabbed window, reveal the selected tab’s close button when someone taps anywhere in the window” (en una ventana con pestañas, revela el botón de cerrar de la pestaña seleccionada cuando alguien toca en cualquier parte de la ventana), y mantenlo hasta que la persona vuelva a un dispositivo señalador.4
Esas seis reglas son la forma escrita de la guía sobre el hover de las dos charlas, con tiempos y excepciones que las charlas no daban: unos segundos antes de que un control revelado se oculte, un segundo toque para activar, y las barras laterales y los botones de cerrar pestañas como los casos que se quedan visibles. La misma revisión reescribió el material sobre el puntero de iPadOS de la página para Liquid Glass, cambió en su texto la forma por defecto del puntero de un círculo a una flecha, reemplazó la columna solo de AppKit de la tabla de cursores por símbolos de AppKit y de SwiftUI, y eliminó la sección de magnetismo del puntero; nada de eso trata de la pantalla de la Mac.4
Lo que todavía no está
Revisado a las 9:51 p.m. PT del 28 de octubre, contra el endpoint de documentación de Apple, la página de aterrizaje, los feeds de noticias y de versiones para desarrolladores y el listado de Tech Talks.56
- La beta de Xcode 27.3. El mosaico de la página de aterrizaje sigue diciendo “Coming soon”, el feed de versiones no lista nada más nuevo que las versiones puntuales del 26 de octubre (macOS 27.2 entre ellas), y las páginas de notas de versión de Xcode 27.3 y de macOS 27.3 devuelven 404. El Xcode más nuevo sigue siendo la release candidate de 27.2, cuyo SDK no he inspeccionado; la advertencia del post de la tarde sobre el deployment target sigue vigente, y el post sobre Xcode 27 tiene el contexto sobre los deployment targets.
- Los tres interruptores de gestos del sistema que demostró la charla de AppKit,
NSWindow.disablesDirectTouchSystemGestures,UIViewController.prefersDirectTouchSystemGesturesDisabledydirectTouchSystemGesturesDisabled()de SwiftUI. Los tres devuelven 404, y ninguno de los dos artículos nuevos los menciona. Los artículos describen cómo un gesto del sistema cancela tu reconocedor; no describen cómo desactivar el gesto del sistema. - Un ActivityKit disponible en macOS. La página del framework lleva una marca del 8 de octubre y no lista la plataforma macOS, y
AlertConfiguration.announcement(_:), de la charla de Live Activities, devuelve 404. La sección del post de la tarde sobre Live Activities sigue tal como está escrita, y el post sobre la máquina de estados sigue siendo solo una implementación en iOS. - El “gesture modernization skill” (skill de modernización de gestos) que la charla de AppKit mencionó en el 12:16. Ninguna página de Apple lo nombra; el comentario
TK skillde la página de aterrizaje sigue ahí.59 - Un enlace desde la página de la charla hasta el artículo, y cualquier declaración de Apple sobre una ruta en el simulador o en Device Hub para el toque en la Mac.
- Una entrada fechada en la página de actualizaciones de la documentación de SwiftUI. Su marca del servidor es de las 2:58 p.m. PT del 28 de octubre, pero su sección más nueva sigue siendo septiembre de 2026 y su entrada sobre gestos es la línea
GestureInputKindsde junio de 2026; cambiara lo que cambiara, no fue una sección fechada nueva.6
Correcciones al post para desarrolladores
El post de la tarde era exacto respecto de las revisiones del mediodía; cinco de sus afirmaciones ya no se sostenían en la revisión de las 9:51 p.m. PT del 28 de octubre. Un bloque de actualización en ese post, publicado junto con este, enlaza aquí, y las afirmaciones están corregidas en su lugar; esta es la lista.
- Los tres mosaicos “Coming soon” de la página de aterrizaje ahora son uno. Los recursos de documentación y los recursos de diseño están publicados; la beta de Xcode 27.3 no.5
- “Adopting gesture recognizers in your app” ya no devuelve 404. Está publicado, y es el documento del que debe leerse la guía de migración.1
- Las páginas de las HIG ya no callan sobre el toque en la pantalla de la Mac. Tres de las cuatro llevan filas del 28 de octubre en el registro de cambios; la página de Live Activities no.4
- La lista de emulación tiene cinco reglas, no las cuatro de la nota técnica de junio; la regla de mantener y luego arrastrar es la nueva.18
- Las vistas exclusivas están activadas por defecto solo para las apps cuyo Info.plist no tiene la clave. Las plantillas macOS App y Document App de Xcode, en Xcode 27.1 y en la beta de 27.2, escriben la clave como
NOpara un proyecto cuyo ciclo de vida de app es el app delegate de AppKit.127
Lo que el post de la tarde decía sobre los SDK, los tres interruptores no documentados y ActivityKit no cambia con nada de lo publicado desde entonces.
Puntos clave
Para desarrolladores de AppKit: ejecuta en Sidecar la prueba de cuatro líneas del artículo y luego busca los cinco símbolos que nombra; una sobrescritura de mouseDown(with:), mouseDragged(with:) o mouseUp(with:) en una subclase de control hace que ese control vuelva a un bucle de seguimiento llame o no a super, y la solución son pares target-action en NSControl.Events con .trackingEndedOutside en el conjunto de ocultación.1
Para un bucle que conservas: el reconocedor solo táctil necesita buttonMask en 0, o un arrastre del mouse inicia el reconocedor y tu mouseDown(with:) nunca se ejecuta; allowedTouchTypes = .direct ya es el valor por defecto.1
Para reconocedores propios: llega a un estado terminal, sobrescribe reset() y location(in:), y depura con -NSGestureRecognizerCrashOnMissingOverrides YES y -NSCrashOnStuckGestureTimeout 2, que debes quitar antes de publicar; en producción, un reconocedor atascado se cancela dos segundos después de que la persona deja de interactuar.12
Para la exclusividad: una app sin la clave es exclusiva; un proyecto creado con la plantilla macOS App de Xcode con el ciclo de vida del app delegate de AppKit (todo proyecto Storyboard o XIB) no lo es, y uno con el ciclo de vida de SwiftUI que viene por defecto sí lo es. Lee tu Info.plist antes de migrar un bucle de seguimiento.27
Para juegos: lee las ubicaciones de los eventos en lugar de sumar deltas, no sobrescribas sendEvent(_:), y ten en cuenta que cada superficie táctil es un GCMouse emulado adicional hasta que NSIsTouchNative vale YES.3
Para diseñadores: el toque es “a secondary input mode” (un modo de entrada secundario); conserva el diseño y el tamaño de los controles; amplía las áreas de toque, no los controles; revela los controles ocultos con un toque cerca de ellos, ocúltalos cuando la persona termina o tras unos segundos sin uso, con las barras laterales y el botón de cerrar de una pestaña como excepciones, y ofrece los esenciales en un menú contextual.4
Preguntas frecuentes
¿Ya está publicado el artículo de migración de AppKit?
Sí. “Adopting gesture recognizers in your app” devolvía 404 al mediodía PT del 28 de octubre y estaba publicado a las 9:51 p.m., listado en las secciones Essentials y Gestures de AppKit y enlazado desde el mosaico de documentación de la página de aterrizaje.15
¿Mi app tiene activadas las vistas exclusivas?
Si tu Info.plist no tiene la clave NSViewGestureRecognizerIsExclusive, sí: “If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES.” (si el Info.plist de la app no incluye la clave, el sistema la trata como YES).2 Un proyecto creado con la plantilla macOS App en Xcode 27.1 o en la beta de 27.2 con el ciclo de vida del app delegate de AppKit (todo proyecto Storyboard o XIB) recibe la clave como build setting con el valor NO; la misma plantilla con el ciclo de vida de SwiftUI que viene por defecto no escribe ninguna clave (análisis del autor de las plantillas en esta Mac).7
¿Qué le pasa a un reconocedor de gestos que nunca termina?
Retiene su vista exclusiva hasta que el sistema lo cancela: un reconocedor “is stuck if it hasn’t reached a terminal state within 2 seconds after the person stops interacting with your app” (está atascado si no llegó a un estado terminal 2 segundos después de que la persona deja de interactuar con la app), y el sistema “automatically transitions it to the NSGestureRecognizer.State.cancelled state.” (lo pasa automáticamente al estado cancelled).2 Configura el ajuste NSCrashOnStuckGestureTimeout mientras depuras para obtener un stack trace en su lugar.2
¿Por qué mi bucle de seguimiento deja de recibir arrastres cuando agrego un reconocedor de desplazamiento?
Porque el buttonMask del reconocedor está en su valor por defecto y reclama el botón principal del mouse: “A mouse drag then begins the gesture recognizer, and AppKit doesn’t call your mouseDown(with:) override, so the tracking loop doesn’t run.” (un arrastre del mouse inicia entonces el reconocedor, AppKit no llama a tu sobrescritura de mouseDown(with:) y el bucle de seguimiento no se ejecuta). Asigna 0 a buttonMask para un reconocedor solo táctil.1
¿Cómo recibe un juego el toque en la Mac?
Como eventos de mouse emulados, en un orden documentado, con un mouseMoved(with:) a la nueva ubicación antes de cada mouseDown(with:), y como un GCMouse adicional cuyos deltas no son continuos. Asignar YES a NSIsTouchNative pasa el juego a reconocedores de gestos y elimina el GCMouse emulado.3
¿Las HIG dan un tamaño de objetivo táctil para la Mac?
No. La página Gestures dice “Make tap targets bigger, not the controls themselves” (haz más grandes las áreas de toque, no los controles) y deja el tamaño a los controles del framework; la cifra de 36 puntos está en las dos charlas, no en las HIG.49
¿De dónde salen los datos de este post?
De los dos artículos de AppKit de Apple, TN3216, las tres páginas de las HIG y la página de aterrizaje, todo consultado a las 9:51 p.m. PT del 28 de octubre de 2026, con la disponibilidad del artículo, las páginas de las HIG y la página de aterrizaje comparadas con revisiones del mediodía y de las 11:49 a.m. del mismo día; del JSON de la documentación de Apple de cada símbolo nombrado; de la nota técnica de junio para la comparación; de las pistas de subtítulos de los Tech Talks para las citas de las charlas; y de los encabezados y las plantillas de proyecto de Xcode 27.1 y de la beta de Xcode 27.2 en mi Mac, que son análisis del autor. La cobertura de prensa no es una fuente.
Fuentes
-
Documentación para desarrolladores de Apple, Adopting gesture recognizers in your app, AppKit. Consultada el 28 de octubre de 2026 a las 9:51 p.m. PT (HTTP 200,
Last-Modified: Wed, 28 Oct 2026 21:30:45 GMT); la misma URL y su endpoint JSON devolvían 404 al mediodía PT del mismo día. Listada en la sección Essentials de la página del framework AppKit, segunda después de “Adopting Liquid Glass”, y bajo “Gesture handling” en Gestures. La primera captura de la Wayback Machine es del 2026-10-29 01:08:12 UTC (fila CDX20261029010812, estado 200; los comprobantes del encabezado sin procesar y del CDX están guardados con la evidencia del post). Secciones citadas: Overview, “Prepare your app for a Mac with a touch display” (la tabla, la prueba de cuatro líneas, la lista de búsqueda), “Filter gesture recognizer candidates”, “Handle touch input”, “Update your event-handling code”, “Replace common mouse-down overrides”, “Handle tracking-loop compatibility”, “Replace your custom tracking loops with gesture recognizers”, “Add touch support to your custom tracking loop”, “Write a custom gesture recognizer”, “Account for gesture recognizer behavior changes” y “Support non-AppKit UI frameworks”. El listado de Swift de este post es una reducción a cuatro líneas del listado de “Add touch support to your custom tracking loop” del artículo. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Documentación para desarrolladores de Apple, Handling AppKit gestures, AppKit. Consultada el 28 de octubre de 2026 a las 9:51 p.m. PT (
Last-Modified: Wed, 28 Oct 2026 05:35:41 GMT); sin historial de revisiones en la página; no enlazada desde TN3212; sin captura en el índice de la Wayback Machine a esa hora. Secciones citadas: Overview, “Track gesture recognizer states”, “Understand gesture recognizer gathering”, “Implement a custom gesture recognizer”, “Prioritize gesture recognizers”, “Delay responder events”, “Configure exclusive views”, “Replace event-tracking loops” y “Diagnose unresponsive gesture recognizers”. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Documentación para desarrolladores de Apple, TN3216: Supporting touch input for games on macOS, historial de revisiones “2026-10-28 First published”, consultada el 28 de octubre de 2026 a las 9:51 p.m. PT (
Last-Modified: Wed, 28 Oct 2026 21:09:50 GMT). Secciones citadas: Overview, “Extend the player experience meaningfully”, “Understand automatic touch support in existing games”, “Understand GCMouse emulation”, “Opt out of mouse emulation”, “Implement touch events”, “Route gestures in your game engine”, “Use standard AppKit controls for automatic touch support” y “Review your touch integration”. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer, Human Interface Guidelines, consultadas el 28 de octubre de 2026 a las 9:51 p.m. PT y comparadas con copias descargadas al mediodía PT del mismo día: Designing for macOS, registro de cambios “October 28, 2026 | Added guidance for touch input.”; Gestures, registro de cambios “October 28, 2026 | Added guidance for gestures and touch input in macOS.”, con la sección de la plataforma macOS y la tabla “macOS trackpad and touch display gestures”; Pointing devices, registro de cambios “October 28, 2026 | Added guidance for revealing hidden controls with touch on Mac, and updated guidance and artwork for iPadOS and macOS.”, con la sección “Touch support for hidden controls”; Live Activities, idéntica byte a byte a la copia del mediodía, con el registro de cambios terminando el 13 de octubre de 2026. Las copias del mediodía de las tres primeras no contienen nada del texto citado. El encabezado
Last-Modifieddel servidor en el nuevo JSON de Gestures diceTue, 27 Oct 2026 05:11:02 GMT, conX-Cache: hit-staleen la primera solicitud a las 9:59 p.m. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer, Get ready for Touch on Mac, consultada el 28 de octubre de 2026 a las 11:49 a.m. PT (tres mosaicos que decían “Coming soon”, con los enlaces en comentarios HTML) y a las 9:51 p.m. PT: “Xcode 27.3 beta” / “Download the latest SDKs to build, run, and test your app.” / “Coming soon”; “Documentation resources” / “Learn how to build and optimize your app for touch input.”, que enlaza el artículo y el ejemplo; “Design resources” / “Create experiences that take advantage of touch user interface patterns.”, que enlaza Gestures, Designing for macOS, Pointing devices y Apple Design Resources, cuya lista de marcos de producto incluye “MacBook Pro PROLED XDR Display”; el comentario
TK skill, presente en ambas descargas. Latest News RSS, elemento más nuevo “Say hello to Touch on Mac”, Wed, 28 Oct 2026 10:00:55 PDT; Releases RSS, elementos más nuevos con fecha Mon, 26 Oct 2026 10:00:00 PDT (iOS, iPadOS, macOS 27.2 (26B123), tvOS, visionOS y watchOS 27.2); el listado de Tech Talks, con 111456, 111467 y 111468 y ninguna otra charla sobre el toque o Live Activities; la página de Bring Touch to your Mac app, sin enlace a ninguno de los dos artículos; todo consultado a las 9:51 p.m. PT. ↩↩↩↩↩↩↩↩↩ -
JSON de la documentación de Apple (
developer.apple.com/tutorials/data/documentation/<path>.json), consultado el 28 de octubre de 2026 a las 9:51 p.m. PT. Encontrados:trackingEndedOutside(macOS 11.0),NSEvent.isTouchSwipeNavigationEnabled(macOS 27.0),NSTextSelectionManager.gesturesForFailureRequirements(macOS 27.0),gestureRecognizer(_:shouldRequireFailureOf:)en NSGestureRecognizerDelegate (macOS 10.10),NSViewGestureRecognizerIsExclusive(“The default value isYES.”),NSIsTouchNative, Mouse, Keyboard, and Trackpad (las dos frases sobre la cadena de respondedores y el toque), Supporting drag and drop through file promises (la frase sobre los toques directos) y SwiftUI updates (Last-Modified: Wed, 28 Oct 2026 21:58:06 GMT, sección más nueva “September 2026”). No encontrados (HTTP 404):appkit/nswindow/disablesdirecttouchsystemgestures,uikit/uiviewcontroller/prefersdirecttouchsystemgesturesdisabled,swiftui/view/directtouchsystemgesturesdisabled(),activitykit/alertconfiguration/announcement(_:),xcode-release-notes/xcode-27_3-release-notes,macos-release-notes/macos-27_3-release-notes. ActivityKit:Last-Modified: Thu, 08 Oct 2026 15:31:35 GMT, plataformas iOS, iPadOS y Mac Catalyst. ↩↩↩↩↩↩↩↩↩↩↩ -
Revisión del SDK y de las plantillas el 28 de octubre de 2026 (análisis del autor), en
MacOSX27.0.sdkdentro de Xcode 27.1 (27A9275): la línea 180 deNSControl.hdiceNSControlEventTrackingEndedOutside = 1 << 7, con la misma línea enMacOSX27.2.sdkdentro de la beta de Xcode 27.2 (27B5019j); la línea 496 deNSEvent.hdice@property (class, readonly, getter=isTouchSwipeNavigationEnabled) BOOL touchSwipeNavigationEnabled API_AVAILABLE(macos(27));; la línea 59 deNSTextSelectionManager.hdeclaragesturesForFailureRequirements; las líneas 116 y 149 deNSGestureRecognizer.hdeclaranshouldRequireFailureOfGestureRecognizer:. Plantillas de proyecto, leídas con plistlib, en Xcode 27.1 (27A9275) y en la beta de Xcode 27.2 (27B5019j), bajoPlatforms/MacOSX.platform/Developer/Library/Xcode/Templates/Project Templates/macOS/Application/:Cocoa App Base.xctemplate/TemplateInfo.plistcontiene la claveINFOPLIST_KEY_NSViewGestureRecognizerIsExclusivecon el valor de cadenaNOenOptions[0] (appLifecycle) / Units / Cocoa / Targets[0] / SharedSettings, y en ningún otro lugar;macOS App Lifecycle.xctemplate, bajoProject Templates/macOS/Base/, define la opciónappLifecyclesinName, conTypepopup,DefaultSwiftUIyValuesSwiftUI,Cocoa;macOS Application UI.xctemplatedefineuserInterfaceconNameInterface:,DefaultSwiftUIyValuesSwiftUI,Storyboard,XIB, y sus unidadesStoryboardyXIB, igual que una de sus unidadesSwiftUI, llevanRequiredOptionsconappLifecycleCocoa;App.xctemplatedesciende deCocoa App Basea través deCore Data App,macOS SwiftUI App,macOS Application UIyCocoa App Native Base;Cocoa Document App.xctemplateescribe un nodoInfo.plist:NSViewGestureRecognizerIsExclusivecuya definición es<key>NSViewGestureRecognizerIsExclusive</key><false/>, dentro de su unidad de ciclo de vidaCocoa;Game.xctemplateasigna aappLifecycleOverrideCocoa;Document App.xctemplatedesciende tanto deCocoa Document Appcomo deCocoa App Base(a través deCore Data Document App,macOS SwiftUI Document-Based App,macOS Application UIyCocoa App Native Base). Solo esos dos archivos de plantilla contienen la clave en cualquiera de los dos Xcode. Los build settingsINFOPLIST_KEY_de Xcode se escriben en el Info.plist generado. ↩↩↩↩↩↩↩↩↩↩ -
Documentación para desarrolladores de Apple, TN3212: Adopting gesture recognizers for Sidecar touch support, historial de revisiones “2026-06-08 First published”, sección “Support third-party UI frameworks”, cuatro viñetas: “A tap emulates a mouse-down followed immediately by a mouse-up”, “A touch and immediate pan emulates trackpad scroll events, even if the UI doesn’t respond to them”, “A long press without movement emulates a rightMouseDown(with:) (optionally followed by rightMouseDragged(with:)) with a rightMouseUp(with:) on touch lift” y “A two-finger pinch or rotation simultaneously emulates both the trackpad magnify and rotate gestures”. Consultada el 28 de octubre de 2026. ↩↩↩
-
Apple Developer, Tech Talks Bring Touch to your Mac app (13:19, Raleigh Ledet) y Design for touch on Mac (16:52, Cas Lemmens), 28 de octubre de 2026. Las citas vienen de las pistas de subtítulos en inglés; el tiempo entre paréntesis es el primer cue del fragmento citado: “drag with a small hold delay” (111468, 11:31), “there’s a gesture modernization skill that has an understanding of the features and changes I’ve talked about” (111468, 12:16), “Touch on macOS uses a touch target of 36 points” (111456, 9:17), “a minimum of 36 points on each axis” (111468, 5:53). El listado de código de la página de la charla “Use NSControl events to detect tracking” (2:24) registra
addTarget(_:action:for:)para[.trackingBegan, .trackingEntered],[.trackingExited, .trackingEndedInside]y.trackingCancelled. ↩↩↩↩↩