Plataformas
macOS
Angular como app de escritorio — NSView de verdad, una ventana que se redimensiona, un puntero con forma, y la única plataforma que puedes comprobar sin arrancar nada.
Angular ejecutándose como app de escritorio: el mismo core, el mismo layout, el
mismo bundle, y NSView de verdad debajo.
an macos # monta el .app y lo abre en esta máquinaan macos examples/desktop # el puntero y el swipean macos examples/media # el mapa y el vídeo./scripts/check-macos.shNo hace falta nada más: aarch64-apple-darwin está en cualquier toolchain
estable y el SDK viene con Xcode. No hay simulador que arrancar, ni dispositivo
que encontrar, ni .xcodeproj. Sin argumento de app, an macos compila
examples/controls. El bundle acaba en build/macos/AngularNativeMac.app, y
--release y --no-launch funcionan los dos.
an add macos le da al proyecto su propio macos/Info.plist, y a partir de ahí
el nombre y el identificador son suyos: MyApp pasa a ser MyAppMac, con
.mac al final del identificador, la misma decoración que aplican
an add tvos y an add visionos. El .app va al directorio de build del
proyecto y no al del SDK, como todo lo demás que produce an.
La app va firmada ad-hoc — codesign --force --sign - — y ese paso no es
opcional. Sin firmar, macOS mata el proceso en el primer mmap de código
generado, que es exactamente lo que hace QuickJS, con un Killed: 9 y ninguna
explicación.
Por qué esto es el host de iOS con otras clases
Sección titulada «Por qué esto es el host de iOS con otras clases»El reloj necesitó un host propio porque watchOS no tiene jerarquía de UIView.
Aquí eso no pasa. AppKit es imperativo, NSView existe, y el modelo de montaje
de este proyecto —crear una vista, ponerla aquí, cambiarle el frame— encaja tal
cual. Así que an-macos es el hermano de an-ios: una vista nativa por nodo
montable, el frame escrito directamente porque taffy ya resolvió el layout, y
MountOp aplicado a la jerarquía.
Aun así es un crate aparte, que no comparte código con an-ios. Hay dos
tablas duplicadas a sabiendas: la traducción de nombres de SF Symbols, y los
bindings declarados a mano para WKWebView, MKMapView y AVPlayerView —seis
métodos cada uno, en lugar de compilar dos crates generados enteros de framework
en cada build. objc2 comprueba cada firma contra la real cuando se envía el
mensaje, así que lo que se ahorra en tiempo de compilación no se paga en
seguridad.
El reparto de hilos es el mismo que en iOS: el hilo principal es dueño de las vistas, QuickJS y el árbol sombra viven en un worker con una pila de 8 MB, y el presupuesto de fotograma para esperar al motor son 12 ms — deliberadamente no bajados para pantallas de 120 Hz.
El FFI son seis funciones — an_runtime_new, _eval, _reload,
_set_viewport, _frame, _free. La cabecera dice lo que es: la del teléfono,
menos los plugins.
Qué dibuja macOS
Sección titulada «Qué dibuja macOS»Los veintiséis primitivos montables. Veintidós son un control del sistema, dos
están ensamblados con vistas del sistema, uno —la cabecera de navegación— macOS
lo pone donde lo guarda, fuera del árbol de vistas, y uno no le toca construirlo
a este crate en absoluto: an-custom es el agujero por el que un plugin
monta su propia NSView. El inventario vive en crates/an-macos/src/support.rs, como una tabla en lugar de
disperso por el match de create, para poder leerlo de una vez;
scripts/check-macos.py lo compara con el enum del core para que no pueda
quedarse atrás.
| Primitivo | macOS |
|---|---|
an-view, an-stack-view |
NSView (AnFlippedView) |
an-text |
NSTextField, configurado como etiqueta |
an-text-input, an-search-bar |
NSTextField, NSSearchField |
an-textarea |
NSTextView |
an-image, an-icon |
NSImageView — los iconos son SF Symbols, por nombre |
an-scroll-view |
NSScrollView |
an-button |
NSButton |
an-switch, an-slider |
NSSwitch, NSSlider |
an-activity-indicator, an-progress-bar |
NSProgressIndicator |
an-segmented-control, an-stepper |
NSSegmentedControl, NSStepper |
an-select, an-date-picker |
NSPopUpButton, NSDatePicker |
an-alert |
NSAlert |
an-web-view |
WKWebView |
an-map-view |
MKMapView |
an-video-view |
AVPlayerView |
Dos están ensamblados, y cuáles son se dice en voz alta:
an-modales unNSViewpor encima de la raíz. Lo que de verdad es modal en macOS es una hoja (beginSheet:) o unNSPanel, y los dos sacan el contenido de la ventana donde lo puso el layout. Como el core ya maqueta el modal a pantalla completa, una capa da el mismo resultado sin dos sistemas de coordenadas discutiendo. El diálogo del sistema —an-alert— sí es unNSAlertreal.an-tab-bares unNSSegmentedControl. macOS no tiene barra de pestañas; un control segmentado es lo que las apps de Mac usan de verdad para cambiar de sección.NSTabViewno vale: eso es la pestaña de documento, con su propio marco y su fondo.
El mapa y el vídeo salieron aquí más baratos que en el teléfono, por el mismo
motivo: en AppKit son vistas. MKMapView hereda de NSView y AVPlayerView
también, así que se unen al árbol como cualquier otra cosa. UIKit no tiene una
vista de vídeo — tiene un AVPlayerViewController — y meter eso en el árbol
significa hacerlo hijo del controlador que preside y recolocar su vista a mano
en cada fotograma, porque nace después de fijar el frame.
El mapa nativo no necesita clave. Quien la pide es MapKit JS, que es otro
producto. Lo que sí necesita permiso es [showsUser], declarado en el
Info.plist del .app como NSLocationUsageDescription: sin esa clave el
sistema deniega la ubicación por su cuenta, el punto no aparece nunca, y no hay
ningún error que mirar. check-macos.py no deja pasar uno sin el otro.
El match de create no tiene comodín: añadir un NodeKind al core rompe
esta compilación en lugar de montar nada en silencio.
La cabecera es la barra de título
Sección titulada «La cabecera es la barra de título»an-navigation-bar no se dibuja dentro del contenido: en un Mac la cabecera de
la pantalla en la que estás vive arriba, en la barra de título de la ventana, y
pintar otra debajo serían dos. Lo que no se tira es lo que escribió la
plantilla. El [title] acaba siendo el título de la ventana, que es donde lo
busca un usuario de Mac:
<an-navigation-bar [title]="'Notas'" />y la ventana pasa a ser «Notas». Cuando esa pantalla se desmonta, la ventana
recupera el título que tenía. Se aplica desde flush y no desde el setter de la
propiedad, porque en ese momento puede que la vista todavía no esté en ninguna
ventana.
El nodo mide cero por cero, y eso está escrito en lugar de salir de que nadie lo mida: una cabecera que no se dibuja pero reserva cuarenta y cuatro puntos deja una franja vacía bajo la barra de título de verdad, y quien la vea no tendrá ni idea de dónde salió.
Lo que la barra de título no tiene es botón de atrás. [showsBack] y
[backTitle] se descartan por eso, y (back) avisa al suscribirse: en un Mac se
vuelve atrás con el menú o con un botón propio de la app, no con una flecha en
la cabecera.
Por eso el inventario tiene dos categorías más junto a «control del sistema»,
«ensamblado con vistas del sistema» y «macOS no lo tiene». Elsewhere —se
honra, pero no con una vista— es la que usa la cabecera, y es la única que la
usa. Plugin es an-custom: una NSView de verdad, pero de una clase de la
que este crate no ha oído hablar nunca, así que no es ni un Native (el nombre
sería mentira) ni un Assembled (no se ensambla nada — la vista llega hecha).
Ningún primitivo está Missing. Si alguien declara alguno alguna vez, check-macos.py lo dice y pide
que se vuelva a escribir el camino del aviso: un nodo que se monta como una caja
vacía en silencio es exactamente lo que este repositorio no permite.
Las tres cosas de escritorio
Sección titulada «Las tres cosas de escritorio»La ventana se redimensiona, y lo hace en vivo
Sección titulada «La ventana se redimensiona, y lo hace en vivo»En un teléfono el viewport cambia al rotar, que pasa de vez en cuando. Aquí cambia mientras alguien arrastra una esquina, sesenta veces por segundo.
viewDidLayout llama a an_runtime_set_viewport en cada paso del arrastre, y la
función descarta los tamaños repetidos: cada cambio real es un viaje de ida y
vuelta al hilo del motor que además espera a lo que hubiera en vuelo, y eso no se
puede pagar en cada layout(). El shell llama sin pensarlo y el filtro está en
Rust, que es donde se conoce el último.
El reloj del fotograma también cambia: el CADisplayLink se le pide a la
vista, no a la pantalla. Un Mac tiene varias pantallas y pueden refrescar a
distintas frecuencias, así que la buena es la de la pantalla donde está la
ventana, y quien sabe eso es la vista. Corre en modo .common para seguir
latiendo mientras se arrastra la ventana o hay un menú abierto, que en AppKit
corren en un bucle de eventos aparte. Pedirle un display link a una vista es
además lo que fija el objetivo de despliegue en macOS 14.
La ventana es de 720×820, con título, cerrable, minimizable y redimensionable, con su marco autoguardado para que el tamaño sobreviva a un relanzamiento. No se fija nunca un tamaño mínimo. Hay exactamente una ventana, y la app se cierra cuando esa se cierra: nada expone una segunda.
Hay un ratón, no dedos
Sección titulada «Hay un ratón, no dedos»Los gestos son los recognisers de AppKit, que no son los de UIKit. press,
doublePress, longPress, pan, pinch y rotation tienen uno cada uno y se
comportan como los de iOS, con los umbrales del sistema. El pinch y la rotación
informan de una velocity de cero, porque el trackpad no la da.
El swipe sí existe, aunque no es un recogniser. AppKit no tiene
NSSwipeGestureRecognizer. El gesto está; lo que falta es algo que colgar de
una vista. Un swipe llega como un evento suelto, swipeWithEvent:, que sube por
la cadena de respondedores como una pulsación de tecla. El sistema lo produce a
partir de lo que el usuario haya configurado en Trackpad, con su umbral y su
número de dedos, así que el juicio de cuándo cuenta sigue siendo de la
plataforma.
Cazarlo exige que el método esté en la clase, y por eso vive con
AnFlippedView —la vista que este host monta para an-view, an-stack-view,
an-modal y el documento de una scroll view— y no con los demás gestos. Un
NSButton es del sistema y no le puedes añadir un método con la app en marcha,
así que un (swipeLeft) puesto directamente sobre un control avisa al
suscribirse y dice dónde ponerlo en su lugar. Eso no es un hueco: un
swipeWithEvent: que un control no maneja sube al siguiente respondedor, que es
su vista padre, así que un swipe sobre un botón llega al <an-view> que lo
envuelve. Y una vista nuestra que no está escuchando esa dirección también lo
pasa en lugar de tragárselo.
Qué dirección es cada signo sale de NSEvent.h y no de una suposición: deltaX
−1 es hacia la derecha y 1 hacia la izquierda; deltaY −1 es hacia abajo y 1
hacia arriba. Vive fuera de la parte que toca AppKit para poder probarlo sin
trackpad, y hay un test que lo fija.
El hover y el cursor están aquí, y son los del sistema. Los dos viven en
NativeVisual, así que en los veinticinco primitivos:
<an-view [cursor]="'pointer'" (hover)="over.set($event.hovered)">(hover)entrega{ hovered, x, y }. Una sola salida con un booleano en lugar de dos, porque lo que hay debajo también es una cosa: unNSTrackingAreada la entrada y la salida por el mismo camino.[cursor]acepta siete nombres de CSS —default,pointer,text,crosshair,grab,grabbing,not-allowed— y cada uno es unNSCursordel sistema. Ninguno está dibujado. Un nombre no reconocido avisa una vez y deja el puntero en paz. Los cursores de redimensionado no están: los que macOS ha tenido siempre están obsoletos, y sus reemplazos llegaron en macOS 15, más tarde que el mínimo de este host.
Los dos se montan sobre un NSTrackingArea y no sobre la vista, y ahí está el
truco: el dueño de un tracking area no tiene por qué ser la vista. Con un
objeto dueño aparte, (hover) y [cursor] funcionan sobre un NSButton del
sistema exactamente igual que sobre uno nuestro, sin subclasear nada. Poner el
cursor por la otra vía —addCursorRect:cursor:— habría exigido sobreescribir
resetCursorRects, que es precisamente lo que no puedes hacerle a un control
del sistema.
El área lleva InVisibleRect, que es lo que hace innecesario reconstruirla en
cada layout: AppKit la mantiene pegada al rectángulo de la vista. Sin eso, un
área se quedaría del tamaño que tenía la vista al suscribirse, y redimensionar
la ventana —que en un escritorio pasa constantemente— haría que el puntero
entrara y saliera donde no hay nada.
Y lleva ActiveInActiveApp, no ActiveAlways: en un Mac los controles solo se
iluminan al pasar por encima cuando la app está delante, y este host no va a ser
la excepción que se comporta distinto del resto del escritorio.
Nada de esto le quita a los controles del sistema lo que hacen por sí solos —un
NSButton se resalta al pasar por encima y un NSTextField convierte el
puntero en una barra de texto— porque son controles de verdad y no dibujos.
Ni (hover) ni [cursor] llegan a iOS ni a Android, y eso no es un hueco: un
dedo no tiene forma. check-wrapper.sh los declara como tales y exige que el
host de escritorio sí los lea. Mira
Props y el envoltorio nativo.
El scroll no tiene delegate
Sección titulada «El scroll no tiene delegate»(scroll) llega con el mismo { x, y } que envía iOS, contando hacia abajo,
así que un componente que lo escuche no necesita saber en qué escritorio está.
Llegar hasta ahí no es lo mismo, eso sí. UIKit tiene UIScrollViewDelegate;
AppKit no tiene nada parecido. Una NSScrollView informa del movimiento
publicando NSViewBoundsDidChange desde su clip view, y solo si a esa clip
view se le ha pedido — postsBoundsChangedNotifications está apagado por
defecto, y su ausencia es una suscripción que no dispara nunca, sin ningún
error por ninguna parte.
El desplazamiento es el bounds.origin de la clip view, y cuenta hacia abajo
por el mismo motivo que todo lo demás aquí: la vista de documento está
invertida.
scripts/check-macos.sh mueve la clip view 90 puntos en una ventana en marcha
y exige que la plantilla diga 90. Un evento sintético de rueda no serviría: un
evento de rueda es una petición, y hasta dónde lo lleva el sistema, en cuántos
fotogramas y con qué elasticidad es cosa de AppKit, así que una comprobación
montada sobre uno mide el scroll del sistema en lugar de si llega la
notificación.
El menú es el del sistema
Sección titulada «El menú es el del sistema»En un teléfono no hay menú. En un Mac siempre lo hay, vive en la barra de arriba
y no dentro de la ventana, así que no cabe en el árbol que monta el core: no hay
NodeKind para él. Lo pone el shell y no se expone a Angular.
Que exista no es cosmético. Los atajos de edición de macOS —⌘X, ⌘C, ⌘V, ⌘Z,
⌘A— no los implementa NSTextField: los despacha el menú por la cadena de
respondedores. Sin un menú Edición, copiar y pegar en un an-text-input no
funciona, sin error y sin nada que mirar. Por eso el menú mínimo incluye Edición
y no solo Salir.
Esquinas que una capa no sabe hacer
Sección titulada «Esquinas que una capa no sabe hacer»Una CALayer tiene un radio, y de ahí salen tres formas. Las cuatro
iguales es el radio y nada más. Unas esquinas redondeadas y el resto cuadradas
sigue siendo un solo radio, con maskedCorners diciendo cuáles lo llevan — la
tarjeta redondeada por arriba, que son la mayoría.
Dos radios de verdad distintos es lo que una capa no puede, y se dibuja: el
contorno va a un CAShapeLayer usado como máscara de la capa, igual que lo
dibuja el host de iOS. Eso cuesta una capa para las vistas que lo piden y nada
para las que no, y se rehace en cada cambio de tamaño, porque una máscara no se
estira.
El trazado se construye con CGPath y no con NSBezierPath, que es lo primero
a lo que echaría mano un fichero de AppKit. El arco de NSBezierPath toma
grados, y su sentido de las agujas del reloj pertenece al sistema de
coordenadas sin invertir que aquí ha volteado cada vista: dos oportunidades de
equivocar una esquina sutilmente a cambio de nada. addArcToPoint toma las dos
líneas que se encuentran y un radio, así que no hay ángulos en los que
equivocarse.
Los cuatro puntos que una etiqueta se guarda
Sección titulada «Los cuatro puntos que una etiqueta se guarda»El layout mide el texto con boundingRectWithSize:, que mide el texto y nada
más. Un NSTextField dibuja ese texto dentro de su celda, y la celda se guarda
unos puntos a cada lado. Hoy son cuatro.
Cuatro puntos suenan a nada y son exactamente el peor tamaño de error. La
etiqueta tiene wraps puesto, así que lo que no cabe se va a la línea
siguiente, y esa línea está fuera de la altura que el layout reservó para una.
El texto no se recorta: sale vacío, sin error, sin rastro, sin nada que
mirar. Aparece en el momento en que una etiqueta cae en una caja de su tamaño
exacto — es decir, en cualquier align-items: center, que es donde apareció.
El espaciado entre letras se mide y no solo se dibuja. Estaba en la clave de la
caché de medición y en ningún otro sitio, así que el host pintaba con el
kerning y el medidor dimensionaba sin él: un espaciado positivo salía más ancho
que la caja que había reservado el layout y partía una palabra antes de tiempo
o se cortaba, uno negativo dejaba un hueco que nadie pidió, y la única
dirección que parecía correcta era el cero. El atributo se añade solo cuando
hay alguno — un kern vacío y ningún kern no son exactamente lo mismo para
Core Text.
La medida se le pide al sistema al arrancar, en el hilo principal, junto al
tamaño natural de todos los demás controles, en lugar de escribirla: es una
medida de AppKit y cambia con la versión y con los ajustes de accesibilidad,
exactamente igual que la altura de un NSSwitch. El texto en sí se mide con el
boundingRectWithSize: de NSAttributedString —no el de NSString, que en
AppKit no tiene las opciones de fragmento de línea que usa el host de iOS— con
los pesos CSS 100..900 mapeados sobre la escala -1..1 de NSFont, cacheado por
texto, fuente y ancho disponible. La cursiva no se resuelve en el medidor:
NSFont no tiene fábrica de cursiva, va por un trait de descriptor, y eso es
más trabajo del que compensa por ahora.
Accesibilidad
Sección titulada «Accesibilidad»Las seis props de accesibilidad aterrizan en el protocolo NSAccessibility, y
la historia entera —incluido el filo más afilado de esta plataforma, que
sobreescribir cualquier cosa le cuesta a la vista el rol que AppKit le estaba
calculando, así que el primitivo vuelve a escribir a mano el rol implícito— está
en Accesibilidad en Apple.
Dos props no tienen forma en AppKit y se rechazan en lugar de aproximarse: el
rol summary, que es una idea de VoiceOver-en-iOS sin contraparte en
NSAccessibility, y el estado busy, cuyo pariente más cercano es el rol
AXBusyIndicator — otra vista, no un estado de esta.
Leer el árbol desde fuera necesita el permiso de Accesibilidad concedido a mano;
el ejemplo es examples/a11y.
Módulos nativos
Sección titulada «Módulos nativos»device está registrado, así que Device.info() resuelve aquí. Tres de los
cinco campos significan exactamente lo que significan en un teléfono; dos no, y
en lugar de devolver el número que más se le parece, la diferencia queda
escrita:
| Campo | En macOS |
|---|---|
platform |
'macos' |
systemVersion |
NSProcessInfo.operatingSystemVersion, como mayor.menor.parche. No operatingSystemVersionString, que dice «Version 15.3.1 (Build 24D70)» y es prosa. |
model |
El identificador de hardware de sysctl hw.model — Mac15,7, MacBookPro18,3. AppKit no tiene UIDevice.model, que en un teléfono responde una clase de dispositivo; aquí esa palabra sería «Mac» para todos los Mac jamás fabricados. |
scale |
El backingScaleFactor de la pantalla en la que la app arrancó. Una ventana se puede arrastrar a una pantalla con otro factor y esto no lo seguirá: se lee una vez, en el hilo principal, porque NSScreen no se puede tocar desde el hilo del motor. Sin ninguna pantalla es 0.0, la misma respuesta que da visionOS, en lugar de un 2.0 inventado. |
locale |
NSLocale.currentLocale, BCP 47. |
No hay un segundo módulo: todo lo demás sería un plugin, y mira más arriba.
Cómo se comprueba
Sección titulada «Cómo se comprueba»Es la única plataforma que se puede comprobar de verdad sin arrancar nada
externo: la app corre en la misma máquina que la compiló.
scripts/check-macos.sh la arranca, la deja montar el árbol y le pide una
captura, sobre examples/controls, examples/desktop y examples/media.
Esa misma propiedad es la que hace posible scripts/check-dev-macos.sh, y es el
único sitio donde el bucle de desarrollo se comprueba de punta a punta: el
servidor sirve, el .app se compila con la URL dentro, la app conecta, se
guarda un fichero y después se le pregunta a la ventana qué está mostrando. Lo
que tiene que estar mostrando es la plantilla nueva y el estado de antes de
guardar — hello-angular deja en pantalla un temporizador en marcha y un @if
que se desplegó a los tres segundos, y un reinicio devuelve los dos a nada.
El modo captura es solo de macOS y lo gobiernan variables de entorno leídas al
arrancar: AN_SCREENSHOT=<ruta> más AN_SCREENSHOT_FRAMES (40 por defecto),
AN_SCREENSHOT_PRESS=x,y, AN_SCREENSHOT_SWIPE=x,y,dx,dy,
AN_SCREENSHOT_HOVER=x,y, AN_SCREENSHOT_WINDOW=1, AN_SCREENSHOT_RELOADS=n y
AN_DUMP_TEXT=1, que registra las cadenas que las NSView montadas están
mostrando de verdad — la única cosa que no se le puede preguntar a un PNG. Las
tres entradas sintéticas disparan dentro de la espera en un orden fijo —la
pulsación a un cuarto de los fotogramas, el swipe a un tercio, el hover a la
mitad— y la captura se toma al final. Usa cacheDisplay(in:to:), que no
necesita permiso de grabación de pantalla, y CGWarpMouseCursorPosition, que no
necesita permiso de accesibilidad. Si nunca se monta nada se rinde a los 600
fotogramas en lugar de colgarse.
Una consecuencia de ActiveInActiveApp: la aserción del hover se salta, y
lo dice, cuando no se puede traer la app al frente. Un salto se informa y no es
un aprobado.
Recursos dentro del bundle
Sección titulada «Recursos dentro del bundle»Lo mismo que en iOS, un directorio más allá. Un
<an-image [source]="'logo.png'"> sale de resources/, en el proyecto —al lado
de macos/Info.plist, no dentro de src/assets/, por los motivos de la
página de iOS— y an macos
copia el árbol a Contents/Resources, junto a main.js, que es el directorio
donde busca NSImage(named:). Se conservan los subdirectorios, se saltan los
ficheros ocultos, y la copia va antes de la firma ad-hoc sin la que esta
plataforma no arranca.
Aquí hay dos nombres reservados y no los cuatro de iOS: el ejecutable vive en
Contents/MacOS y el Info.plist en Contents, así que un recurso no puede
chocar con ninguno de los dos. Un recurso llamado main.js o dev-server.txt
sigue parando la compilación. Un [source] que nombra un fichero que no está se
avisa una vez, por su nombre —images.rs, con la misma forma que los avisos de
medición—, en lugar de dejar un NSImageView vacío que se lee como una imagen
que aún está cargando.
examples/controls lleva uno, así que scripts/check-macos.sh fotografía una
ventana con un PNG del bundle de verdad dentro.
Llevarlo a otro Mac
Sección titulada «Llevarlo a otro Mac»an macos . --sign # Developer ID, hardened runtimean macos . --notarize --dmg # enviado, grapado y empaquetadoLa firma ad-hoc de más arriba es lo que deja correr la app aquí. En
cualquier otro sitio Gatekeeper quiere una firma de Developer ID y un tique de
notarización, y el hardened runtime que exige la notarización no es un cambio de
flag: prohíbe mapear memoria ejecutable y escribible, que es lo primero que hace
el motor de JS. Así que la compilación firmada declara
com.apple.security.cs.allow-jit. Sin eso la app muere al arrancar con un
Killed: 9 que no menciona ningún entitlement — el mismo fallo que la firma
ad-hoc existe para evitar, con la única cara que nadie reconoce.
El .dmg se firma y se notariza por derecho propio, porque la imagen es el
fichero que se descarga. Empaquetar uno sí se comprueba aquí; firmar y notarizar
necesitan un certificado de Developer ID de pago y no se han ejecutado nunca.
Mira Firma y distribución.
Lo que falta
Sección titulada «Lo que falta»- Una vista de plugin no se mide por su contenido.
<an-custom>monta lo que un plugin registró conAnPluginViews.register, llenando la caja que el layout le dio al nodo — y un nodo sin tamaño sale a cero, lo que parece un plugin que no funciona. Un nombre que nadie registró no monta nada y lo dice una vez, nombrando el nombre. Mira una vista que trae un plugin. - Los entitlements de un plugin necesitan una firma real. El host carga
plugins, pero un
.appfirmado ad-hoc que lleve un entitlement respaldado por un perfil lo mata el sistema al lanzarse, así que una compilación sin firmar deja fuera esas claves y avisa nombrando la clave, el plugin y el flag que las devuelve. Mira Plugins en el Mac y en el reloj. enableden cualquier cosa que no sea unNSControlno hace nada: AppKit no tieneuserInteractionEnabled.animateDelayse funde en la duración —NSAnimationContextno tiene retardo— yanimateEasingsolo se aplica paraease-in-out.colorenan-switch,an-activity-indicatoryan-progress-bares el color de acento del sistema y no se puede fijar por vista.resizeMode: 'cover'encaja dentro en lugar de recortar, porqueNSImageViewno puede recortar.sheetenan-alertsolo cambia el estilo de la alerta: macOS no tiene hoja de acciones. La alerta se presenta conbeginSheetModalForWindow:y nunca conrunModal, que congelaría el bucle de eventos que mueve el fotograma.(dismiss)enan-modal,(refresh)enan-scroll-viewy(back)tanto en la stack view como en la barra de navegación avisan todos al suscribirse, cada uno con su motivo.safeArearesponde una vez con los cuatro insets a cero, a propósito.- Props aceptadas y descartadas con un aviso único:
keyboardType,returnKeyType,autoCapitalize,autoCorrect,secureTextEntry,thumbColor,maximumTrackColor,refreshing,bounces,transition,showsBack,backTitle,presentationyunselectedColor. Cualquier cosa fuera de esa lista imprime «unknown prop», que las comprobaciones tratan como un fallo.