Referencia
Eventos
Cada output que puede escuchar una plantilla, la carga exacta que lleva, cuándo se entrega, y qué plataformas no pueden entregarlo — con la razón que dan cuando te suscribes igualmente.
Un output aquí no es un evento del DOM con un cast encima. Cada uno está tipado, cada uno lleva una carga declarada, y cada uno es un observable frío: el reconocedor o el listener de la plataforma se engancha cuando Angular se suscribe y se suelta cuando la vista se destruye. Una vista a la que nadie escucha no cuesta nada.
import type { NativePanEvent } from '@angular-native/primitives'
onPan(event: NativePanEvent) { … }Cuándo llegan
Sección titulada «Cuándo llegan»Nunca en mitad de un frame. Un evento nativo se encola y se le entrega a JavaScript al principio del frame siguiente, así que todo lo que pasó entre dos vsyncs se procesa en un turno y produce un commit.
Un arrastre da un (pan) por frame, no uno por muestra táctil. Las muestras de
más costarían cada una una pasada de detección de cambios y producirían un árbol
idéntico al que ese frame va a montar de todas formas.
En todas las primitivas
Sección titulada «En todas las primitivas»Viven en la clase base, así que las 25 los tienen.
| Output | Carga |
|---|---|
(press) |
{ x, y } — puntos, relativos a la vista que lo recibió |
(doublePress) |
{ x, y } |
(longPress) |
{ x, y } — una sola vez, cuando el sistema decide que cuenta |
(pan) |
{ x, y, translationX, translationY, velocityX, velocityY, state } |
(pinch) |
{ scale, velocity, state } — scale es relativa al inicio del gesto |
(rotation) |
{ rotation, velocity, state } — radianes desde que empezó |
(swipeLeft) (swipeRight) (swipeUp) (swipeDown) |
{ x, y } |
state es 'begin' | 'move' | 'end' | 'cancel'. cancel no es un fallo: el
sistema se ha llevado el gesto porque ha ganado otro. velocity va en puntos por
segundo, útil para dejar que algo siga rodando cuando se levanta el dedo.
La traslación se mide desde donde empezó el dedo, no desde el evento anterior.
La historia entera —reconocedores por plataforma, qué significa cancel en la
práctica, y cómo se combinan con [animate]— está en
gestos y animación.
Layout y pantalla
Sección titulada «Layout y pantalla»| Output | Carga | |
|---|---|---|
(layout) |
{ x, y, width, height } |
El frame resuelto, relativo al padre, cada vez que cambia. |
(safeArea) |
{ top, right, bottom, left } |
Los márgenes que reserva el sistema — el teclado incluido. |
(layout) lo emite el núcleo, no una plataforma: el núcleo es quien calcula
el frame, así que funciona igual en todas partes y nunca se implementó dos veces.
(safeArea) cambia al rotar, al entrar en pantalla partida y mientras el teclado
se anima. Ver el área segura y el teclado.
Foco y puntero
Sección titulada «Foco y puntero»| Output | Carga | |
|---|---|---|
(focus) |
{ value? } |
value solo cuando la vista es un campo de texto. |
(blur) |
{ value? } |
|
(hover) |
{ hovered, x, y } |
Solo escritorio. Un output y no dos, porque un NSTrackingArea entrega las dos cosas. |
El foco vivía antes solo en an-text-input, porque en un móvil el foco es del
teclado. En una tele es la plataforma entera —el mando recorre las vistas
enfocables y no hay otra forma de resaltar la que está debajo del cursor— así que
se subió a la base.
El reloj
Sección titulada «El reloj»| Output | Carga | |
|---|---|---|
(crown) |
{ delta, offset, velocity } |
La corona digital, mientras gira. |
(crownIdle) |
— | Ha parado. Sin esto no hay forma de saber cuándo dejar de moverse. |
delta es el cambio desde el último aviso, que es casi siempre lo que se quiere;
SwiftUI solo entrega el acumulado, y offset es ese acumulado desde que la vista
cogió el foco.
Está en la clase base y no en un control porque la corona va a la vista que tenga el foco, sea cual sea — el equivalente a girar la rueda del ratón encima de algo.
Por primitiva
Sección titulada «Por primitiva»| Primitiva | Output | Carga |
|---|---|---|
an-switch |
(onChange) |
boolean |
an-slider |
(valueChange) |
number |
an-stepper |
(change) |
{ value: number } |
an-segmented-control |
(change) |
{ index: number } |
an-select |
(change) |
{ index: number } |
an-date-picker |
(change) |
{ value: number } |
an-tab-bar |
(select) |
number — el índice |
an-text-input |
(valueChange) |
string |
(submit) |
string — la tecla de retorno |
|
an-textarea |
(change) |
{ value: string } |
an-search-bar |
(input) |
{ value: string } |
(submit) |
{ value: string } |
|
an-scroll-view |
(scroll) |
{ x, y } |
(refresh) |
— tirar para recargar | |
an-image |
(load) |
{ width, height } — el tamaño intrínseco de la imagen |
an-stack-view |
(back) |
— el gesto del borde, o el botón de Android |
an-navigation-bar |
(back) |
— el botón de volver |
an-modal |
(dismiss) |
— cerrado por el usuario y no por la prop |
an-alert |
(select) |
number — qué botón |
Seis de estos entregan el valor en vez de un objeto —(onChange),
(valueChange) tanto en el slider como en el campo de texto, y (select) en la
barra de pestañas y en la alerta— porque no había nada más en la carga que le
hiciera compañía y un $event.value en cada uno era ruido. El resto conserva el
objeto, para que añadir un segundo campo más adelante no rompa nada.
Lo que una plataforma no puede entregar
Sección titulada «Lo que una plataforma no puede entregar»Un evento que una plataforma no puede dar avisa al suscribirse, y el aviso lleva la razón. No se queda callado, y la razón es casi siempre del SDK y no una decisión tomada aquí.
| Evento | Por qué no |
|---|---|
(pinch), (rotation) |
La superficie del mando es de un solo toque, y UIPinchGestureRecognizer y UIRotationGestureRecognizer no están en el SDK de tvOS. |
(pan) y los cuatro swipes sí funcionan: la superficie reporta un arrastre.
(press) llega del botón central, así que una vista que no puede coger el foco
no se puede pulsar nunca.
(swipeLeft) y compañía funcionan, pero solo en vistas que sean de este
host. AppKit no tiene NSSwipeGestureRecognizer; el gesto llega como
swipeWithEvent: por la cadena de respondedores, y eso hay que atenderlo en la
clase. Enganchar un swipe directamente a un control del sistema —un NSButton,
un NSSlider— avisa al suscribirse y dice que lo pongas en un an-view que lo
envuelva.
watchOS
Sección titulada «watchOS»El único host que no es una jerarquía de vistas, y el de la lista más larga.
| Evento | La razón que da |
|---|---|
(pinch) |
MagnifyGesture está marcado @available(watchOS, unavailable), y dos dedos no caben en una pantalla de 40 mm. |
(rotation) |
RotateGesture está marcado @available(watchOS, unavailable). |
(back) |
Fuera de un NavigationStack el reloj no da arrastre desde el borde, y montar uno metería el layout de SwiftUI dentro del de taffy. |
(refresh) |
En un reloj una lista no se recarga tirando de ella: eso se hace con la corona, que ya llega como (crown). |
(scroll) |
El ScrollView de SwiftUI no publica su desplazamiento en watchOS 11, que es el mínimo de este shell. |
(safeArea) |
Una app de reloj ocupa la pantalla entera y el sistema no reserva márgenes que se puedan preguntar. |
(focus), (blur) |
Todavía no: en un reloj el foco es el mismo que decide quién tiene la corona, y dos dueños harían que la corona saltara a otro sitio mientras escribes. |
Un móvil no tiene puntero
Sección titulada «Un móvil no tiene puntero»(hover) es solo de escritorio, y la prop [cursor] que va con él, también. Un
dedo no tiene forma y nada sobrevuela antes de tocar. Eso está declarado, no
olvidado.
El aviso que deliberadamente no se imprime
Sección titulada «El aviso que deliberadamente no se imprime»Angular registra un listener de elemento para cada output que aparece en una
plantilla, incluidos los que no son eventos de plataforma en absoluto:
onChange, valueChange. Avisar de esos sería avisar en cada arranque de algo
que funciona perfectamente, y un aviso que sale siempre es un aviso que nadie
lee.
Así que los hosts llevan una lista de los eventos que saben enganchar, y solo avisan del primer tipo: algo que una plantilla le pidió de verdad a la plataforma y que esta plataforma no da.