Rendimiento e interoperabilidad de SwiftUI en iOS 27
Un LazyVStack no sabe qué tan alto es. Estima su propia altura a partir del tamaño promedio de las vistas que ya ha colocado y la cantidad que espera que falten, y luego corrige esa estimación en vivo a medida que haces scroll.1 Tres sesiones de la WWDC26 del equipo de UI Frameworks toman ese único hecho (y sus análogos en gráficos e interoperabilidad) y lo convierten en un modelo funcional de cómo se comporta SwiftUI bajo carga en iOS 27: cómo el scroll se mantiene fluido, cómo se componen los efectos de GPU y cómo SwiftUI encaja en una app de AppKit o UIKit que ya enviaste.
Las tres sesiones se leen como un solo argumento expuesto de tres maneras. Los lazy stacks rinden bien cuando dejas de pelear contra su estimación; los efectos de shader se componen cuando tratas cada modificador como una etapa de una canalización; y la interoperabilidad funciona cuando dejas que @Observable y los protocolos representables sostengan la costura. Ninguna de las tres es el anuncio de una función. Cada una es un mecanismo explicado lo suficientemente bien como para que puedas predecir el framework en lugar de adivinarlo.
TL;DR / Puntos clave
- Un
LazyVStacksolo evalúa las vistas que llenan el rect visible; las alturas fuera de pantalla y el offset del contenido son estimados, así que las lecturas absolutas de offset de contenido son inestables y deberías preferir APIs de visibilidad relativa como.onScrollTargetVisibilityChange.12 - El prefetching reparte el trabajo de mostrar una vista a lo largo de varios frames antes de que aparezca; configura las vistas en su inicializador (no en
onAppear) para que el trabajo precargado no se descarte.1 - Evita una cantidad dinámica de subvistas en una hoja de
ForEach: filtrar con un condicional en elbodymantiene vivas las vistas por índice, así que filtra a nivel de datos (unPredicatesobre unQuery) en su lugar.1 - SwiftUI expone tres puntos de entrada de shaders (
colorEffect,distortionEffect,layerEffect), que aumentan en potencia; sololayerEffectpuede muestrear píxeles vecinos, que es lo que necesitan el blur y el domain-warp.3456 - Los shaders no tienen estado, así que la animación viene de pasarle como parámetro una marca de tiempo de un
TimelineView; nada se traslada entre frames.37 - AppKit y UIKit obtienen redibujado automático desde
@Observable(se acabó elneedsDisplaymanual), y SwiftUI se integra en una app existente a través deNSHostingView,NSGestureRecognizerRepresentable,NSHostingMenuyNSHostingSceneRepresentation.8910
Los lazy stacks funcionan con estimaciones
LazyVStack hace el layout de arriba hacia abajo y se detiene una vez que el rect visible está lleno, estimando el resto.
Lo primero que hay que interiorizar sobre un lazy stack es que cambia corrección por eficiencia a propósito. A diferencia de un VStack, un LazyVStack no evalúa ni renderiza vistas que no están visibles; coloca sus vistas de arriba hacia abajo y se detiene una vez que el rect visible está lleno, agregando vistas a medida que entran con el scroll y quitándolas a medida que salen.1 La recompensa es obvia. El costo es sutil: como el stack nunca carga todas las vistas, las alturas de las vistas fuera de pantalla se estiman a partir del promedio de lo que vino antes, el ancho ideal colapsa al ancho de la primera subvista, y el espacio por encima de la región visible es en sí mismo aproximado.1
Esa estimación no es un bug que esquivas una vez; es el sustrato sobre el que se asienta cualquier otra decisión del lazy stack. La sesión 321 hace concreta la consecuencia con un cambio de orientación. Rota un iPhone y la vista visible más alta se mantiene anclada, pero el stack todavía no ha medido el layout nuevo exacto de las vistas que están por encima de ella. Haz scroll de vuelta hacia arriba y el stack debe reconciliar: corrige el espacio estimado por encima de la región visible y actualiza el offset de contenido de la scroll view por la misma cantidad, de modo que el offset de contenido en la parte superior aterrice en cero.1 El lazy stack y la scroll view que lo contiene coordinan posición y offset con precisión para que, a medida que las estimaciones se actualizan, la posición relativa de las subvistas visibles nunca dé un salto.1
El corolario accionable es una regla sobre en qué APIs de scroll confiar. Como el offset absoluto de contenido es estimado, leerlo (con .onScrollGeometryChange, por ejemplo, para ocultar un botón después de 100 puntos) te da un umbral que va a la deriva a medida que las estimaciones se asientan.2 La señal estable es la visibilidad relativa. El modificador .onScrollTargetVisibilityChange se dispara cuando cambia el conjunto de subvistas visibles en la scroll view, así que un botón de “desplazarse para mostrar” puede ligar su visibilidad a qué filas están en pantalla, con un umbral (la sesión usa 80%), en lugar de un conteo de píxeles inestable.21 La misma lógica condena a .scrollTransition: una transformación que empuja una vista fuera de su frame original puede hacer que el stack crea que una vista visible está fuera de pantalla y la descarte antes de tiempo, así que cualquier transición de scroll debe evitar que vistas que normalmente no serían visibles sean empujadas hacia el rect visible.111
Por qué tus structs de vista no son las subvistas
Las subvistas que carga un lazy stack no se mapean uno a uno con los structs de vista que escribiste. Un ForEach de StepView se resuelve en un StepView por paso, pero si el body de cada StepView devuelve dos vistas de nivel superior (un diagrama e instrucciones) sin un layout que las contenga, el stack carga cada una de ellas por separado.1 El número que le importa al stack es el conteo de subvistas resueltas, no el conteo de structs.
La trampa es un conteo de subvistas dinámico. Si un StepView devuelve una subvista o cero según un valor del entorno, el stack ya no puede confiar en los índices, porque el conteo de vistas anteriores podría cambiar. Así que mantiene vivas las instancias anteriores de StepView por si acaso, lo que significa que un cambio de entorno no relacionado puede disparar evaluaciones del body para vistas que salieron de pantalla con el scroll, y el stack no liberará su estado.1 La solución es mover el filtro fuera de la vista y hacia los datos: si usas SwiftData, pon la condición en un Predicate sobre el Query para que el conteo de subvistas se conozca sin construir ninguna vista.1 Desempaquetar un opcional en un body tiene el mismo efecto de mantener-más-tiempo-vivo; el movimiento más limpio es mostrar una ContentUnavailableView más arriba en lugar de dejar que el lazy stack retenga filas parcialmente resueltas.1
El prefetching es el mecanismo que hace que las estimaciones se sientan rápidas. Mientras haces scroll, una scroll view solo tiene hasta el deadline del frame para actualizar el offset, renderizar vistas y ejecutar tu trabajo de cambio de offset; si mostrar una vista nueva revienta ese presupuesto, el frame se cae y ves un tirón.1 Para prevenirlo, el lazy stack verifica si hay tiempo de sobra y, si lo hay, hace parte del trabajo de una vista que está por aparecer de forma anticipada (evaluando su body y su layout, incluso repartiendo un LazyHStack anidado entre frames), de modo que para cuando la vista aparece, la mayor parte del trabajo ya está hecha.1 Por eso la sesión es enfática sobre onAppear: si configuras una vista en onAppear, descartas el trabajo precargado y fuerzas que se rehaga cuando aparece, a veces atrayendo más vistas de las necesarias y degradando el scroll. Configura la vista en su inicializador para que llegue en un estado razonable, y reserva onAppear para trabajo genuinamente ligado a la aparición, como obtener la siguiente página en un scroll infinito.1 En un scroll invertido, un body puede incluso ejecutarse durante el prefetching mientras que onAppear nunca se dispara en absoluto.1
Los gráficos avanzados son solo una canalización
La sesión 322 reencuadra los “gráficos avanzados” como composición. Cada modificador de SwiftUI es una tubería que toma datos de entrada, los transforma y los pasa adelante; el resultado avanzado vive en cómo conectas las tuberías, no en ninguna API compleja individual.3 La sesión construye una vista de letras en vivo al estilo de Apple Music encadenando etapas ordinarias: aplica blur a la portada para que retroceda, ejecuta un shader sobre ella, impulsa ese shader con el tiempo y sincroniza el scroll de una transcripción con la misma fuente de tiempo.3
La etapa del shader es donde está la verdadera elección. SwiftUI llama a las funciones de shader de Metal a través de tres puntos de entrada de efectos que escalan en capacidad. colorEffect transforma el color de cada píxel a partir de su posición y su color original, lo cual basta para algo como una conversión a escala de grises.4 distortionEffect en cambio mapea una posición a otra (le dices a SwiftUI que muestree el color de esta posición desde aquella posición), lo que maneja deformaciones geométricas sin color involucrado.5 layerEffect es el más flexible: le entrega al shader la capa entera de la vista, de modo que el píxel de salida puede muestrear a sus vecinos o la región completa, que es exactamente lo que requieren el blur y las deformaciones más ricas.63
El fondo con domain-warp de la sesión usa layerEffect. Un offset float2 uniforme desplaza cada píxel por la misma cantidad, lo que solo desliza la imagen; el movimiento orgánico necesita variación por píxel, así que el shader muestrea una NoiseTexture precalculada (pasada como una imagen, que llega del lado de Metal como un texture2d) cuyos canales rojo y verde suministran un offset distinto en X y Y en cada coordenada UV.3 Muestrear el ruido una vez retuerce la imagen; muestrearlo dos veces, la segunda vez en una posición que el primer muestreo desplazó, produce blobs que fluyen. Esa técnica de segundo orden es el domain warping, y la sesión apunta a su app de ejemplo descargable con una previsualización en vivo de los parámetros.3
Dos hechos del framework hacen que la animación funcione. Los shaders no tienen estado: no guardan memoria del frame anterior, y la salida depende solo de los parámetros que les pasas.3 Así que el movimiento no puede venir desde dentro del shader; tiene que ser inyectado, y un TimelineView es la tubería que lo suministra, disparándose cada frame con una marca de tiempo en un schedule de animación.73 Pasa esa marca de tiempo al shader, súmala a la posición de muestreo del ruido, y el patrón fluye. El lado de la transcripción reutiliza la misma fuente de tiempo desde la otra dirección: la marca de tiempo de reproducción elige la línea actual (en negrita y nítida, el resto atenuado), y un onChange mantiene esa línea centrada a medida que avanza el tiempo.123 La marca de tiempo flotante sobre la línea activa se posiciona no con offset (que necesitaría los tamaños de ambas vistas) sino con un override de alignment-guide que redefine el punto de un alineamiento de forma semántica, de modo que el borde superior de la subvista se adjunta al borde inferior de su contenedor sin un offset manual.133
SwiftUI se integra en una app de AppKit o UIKit
La sesión de interoperabilidad abre con un punto que reencuadra toda la cuestión de la adopción: la mayoría de las apps ya usan SwiftUI implícitamente. En el nuevo diseño, controles de AppKit como NSSlider, NSSwitch y NSSegmentedControl se renderizan con SwiftUI por debajo, y Liquid Glass también comparte gran parte de su implementación entre frameworks a través de SwiftUI.8 Así que “adoptar SwiftUI” es menos una reescritura que una decisión sobre dónde hacer la costura explícita.
El primer paso no necesita SwiftUI en absoluto. AppKit y UIKit ahora observan los tipos @Observable automáticamente: marca una clase de modelo como @Observable, lee sus propiedades dentro de un método de dibujo como drawKnob, y AppKit rastrea cada acceso y redibuja cuando cualquier propiedad accedida cambia, retirando el needsDisplay = true manual que solías escribir cada vez que el valor de un slider afectaba la apariencia de otro.814 La observación se extiende más allá de draw(_:) hacia updateConstraints(), layout(), updateLayer() y los equivalentes de NSViewController, y UIKit llega todavía más lejos, hacia UIButton, UICollectionViewCell y más.8 Está activada por defecto en los lanzamientos de 2026 y es retro-desplegable a macOS 15 (NSObservationTrackingEnabled) e iOS 18 (UIObservationTrackingEnabled) vía Info.plist.8
Una vez que el modelo es @Observable, la costura real de SwiftUI es pequeña. La sesión reconstruye un selector de color basado en sliders como un control circular de SwiftUI dibujado con Canvas (una API de modo inmediato análoga a drawRect, con withCGContext para reutilizar código existente de Core Graphics), reutilizando el mismísimo @Observable ColorModel.158 Para incrustarlo donde AppKit espera una vista, envuélvelo en NSHostingView, una subclase de NSView; como el modelo ya impulsa las actualizaciones, esa envoltura es todo lo que se requiere.168 El código de gestos existente se traslada sin reescribirlo: un ForceClickGestureRecognizer alcanza una vista de SwiftUI a través de NSGestureRecognizerRepresentable (implementa makeNSGestureRecognizer y handleNSGestureRecognizerAction), luego se adjunta con el modificador ordinario .gesture y coexiste con el propio gesto de arrastre de SwiftUI.178 La misma familia de representables incluye NSViewRepresentable para incrustar NSViews en la otra dirección.8
La costura escala hasta menús y escenas. Una View de SwiftUI que contiene un Button y un Picker se convierte en un menú real a través de NSHostingMenu (una subclase de NSMenu), establecida como el submenú de un NSMenuItem agregado al menú principal, con keyboardShortcut dando a la acción una ruta sin gestos para dispositivos de entrada que no pueden hacer force-click.188 Escenas enteras de SwiftUI también se adjuntan: un MenuBarExtra alcanza una app existente a través de NSHostingSceneRepresentation, agregado vía addSceneRepresentation en applicationWillFinishLaunching, con el Toggle de una escena Settings controlando si el extra se inserta y la acción de entorno openSettings() abriendo los ajustes desde un @IBAction.198 El punto de cierre de la sesión es el que sostiene todo el peso: cada API que cubre se incluye en los lanzamientos de 2026 o anteriores, y no hay ninguna expectativa de que una app deba ser enteramente SwiftUI para beneficiarse.8 El empuje de modernización tiene un filo más duro en otra parte del ciclo, sin embargo: iOS 27 hace del ciclo de vida basado en escenas de UIKit un requisito de lanzamiento, así que una app que reconstruyas con el SDK más reciente no logra lanzarse si nunca adoptó las escenas.
Lo que el equipo de SwiftUI agregó en los labs
Dos aclaraciones de los group labs del equipo de UI Frameworks de la WWDC26 afinan el modelo de invalidación que describen las sesiones. Ambas están parafraseadas a partir de una grabación transcrita localmente; Apple no publica subtítulos oficiales para los labs, la misma brecha de subtítulos que recorrió el propio Group Lab del equipo de Swift donde los ingenieros del lenguaje respondieron las preguntas sobre concurrencia y hoja de ruta.
Mover código fuera de body hacia una propiedad computada no compra ningún beneficio de invalidación. SwiftUI igual vuelve a ejecutar la propiedad cada vez que vuelve a ejecutar body, así que el movimiento es una ganancia de legibilidad y nada más.20 La frontera de rendimiento aparece un nivel más arriba: extrae un tipo de vista separado y SwiftUI puede invalidarlo de forma independiente, volviendo a ejecutar solo esa vista cuando sus entradas cambian en lugar de todo el body que la contiene. Recurre a una nueva vista, no a una nueva propiedad, cuando quieres que SwiftUI haga menos trabajo.
Cada cambio de entorno invalida todas las vistas que leen ese valor del entorno. Leer el entorno es barato; la rotación del entorno no lo es.21 Mantén los valores que cambian rápido fuera del entorno (el ejemplo del panel es la hora actual), porque un valor que se actualiza cada frame arrastra a cada lector a una reevaluación cada vez que se mueve. Pasa los valores volátiles por la ruta que los necesita y deja que el entorno cargue las cosas que se quedan quietas.
Una sesión de lab posterior agrega tres detalles de mecanismo más, todos parafraseados a partir de una grabación transcrita localmente del SwiftUI Group Lab de la WWDC 2026, para el cual Apple no publica subtítulos oficiales.
La evaluación parcial del grafo explica adónde va realmente el trabajo precargado. El panel describió un lazy stack evaluando los body de las celdas próximas en el tiempo de frame que queda después de que el frame actual se renderiza, y luego deteniéndose justo antes de que comience el siguiente frame.22 El detalle que señalaron: un onAppear que reconfigura una celda de modo que haya que hacerle el layout de nuevo, porque cambia el tamaño de la celda, descarta ese trabajo precargado. Su orientación fue hacer el trabajo de dimensionamiento en el inicializador de la celda, no en body ni en onAppear, para que el prefetch sobreviva.22
El modelo mental de la interoperabilidad vino de un panelista con más de una década en UIKit. UIKit hace el layout de arriba hacia abajo, desde la ventana hacia adentro hasta las hojas, mientras que SwiftUI construye de abajo hacia arriba, desde el nodo más interno hacia afuera.22 Cuando los dos se entrelazan, el panel llamó a la disposición de capas alternadas un sándwich o un pastel, que es la razón por la que el entrelazado profundo se vuelve sutil: cada framework quiere conducir el pase de layout desde el extremo opuesto.22
El punto sobre el entorno de arriba obtuvo un ejemplo vívido del mundo real. Cuando se les preguntó por el peor mal uso del entorno que habían visto, el panel citó poner la posición de scroll en el entorno, que se actualiza en cada frame mientras se hace scroll y por lo tanto invalida a cada lector en cada frame.22
Una segunda sesión del SwiftUI Group Lab agregó cuatro detalles de mecanismo más, todos parafraseados a partir de una grabación transcrita localmente del SwiftUI Group Lab de la WWDC 2026 (sesión 2); Apple no publica subtítulos oficiales para los labs.
El modificador onGeometryChange(for:of:action:) se lee mejor de lo que suena por cómo sus dos clausuras dividen el trabajo. La clausura de transformación se ejecuta con geometría en vivo en cada frame, pero solo el valor que devuelve determina si la acción se dispara, ya que el tipo del resultado es Equatable y la acción se ejecuta solo cuando ese valor cambia.23 Así que devolver un valor grueso (un bucket de tamaño o un breakpoint de layout, no el tamaño crudo) convierte una señal de tasa de frames en una que se dispara dos veces, en los umbrales, en lugar de continuamente. El panel emparejó esto con una advertencia de que GeometryReader es costoso para las subvistas que envuelve y debería confinarse a un background para que mida sin conducir el layout principal.23
Una buena dynamic property puede reemplazar la mayor parte del trabajo de onChange por completo. El método update() de un DynamicProperty se ejecuta inmediatamente antes del body de la vista, así que un property wrapper personalizado puede entregar un valor ya cacheado (el ejemplo del panel fue una imagen) de forma síncrona en ese punto y saltarse el viaje de ida y vuelta de onAppear y el re-render que dispara.24 El planteamiento del panel fue que la mayoría de los usos de onChange pueden reemplazarse por una dynamic property bien construida.24
Una vista que dibuja fuera de los límites de layout que reporta dentro de un ScrollView puede ser descartada (culled), porque el sistema decide que está fuera de pantalla a partir de los límites que se le dijeron, no de dónde realmente pinta.25 El panel nombró esto como el comportamiento de falla concreto detrás de dropdowns y overlays personalizados que desbordan su vista anfitriona y luego se desvanecen a mitad del scroll, y señaló que el mismo riesgo se aplica al contenido de overlay que se extiende más allá de su ancla.25
Presentar un overlay de pantalla completa por encima de todo, incluidos los sheets, no tiene una respuesta limpia puramente en SwiftUI. La orientación del panel fue bajar a un nuevo UIWindow a través del ciclo de vida basado en escenas de UIKit, con el principio más profundo de que la última ventana gana y debe haber una única fuente de verdad para lo que se ubica encima.26 Agregaron que la mejor solución a menudo es restaurar el navigation stack que el usuario espera en lugar de arrojar una cubierta disruptiva por encima de todo.26
Qué adoptar primero
Un lanzamiento explicado como mecanismo premia ordenar por apalancamiento, no por novedad.
- Cambia las clases de modelo a
@Observableen tu código de AppKit/UIKit. Elimina las llamadas manuales aneedsDisplay, da a cada método de dibujo y layout que observa redibujado automático, y es el prerrequisito que hace trivial un futuro drop-in deNSHostingView.814 Es el movimiento de menor riesgo y mayor recompensa inmediata de las tres sesiones. - Audita los lazy stacks en busca de conteos de subvistas dinámicos. Cualquier hoja de
ForEachque devuelva condicionalmente cero o una subvista, o desempaque un opcional en subody, está manteniendo vivas las vistas por índice; mueve el filtro a unPredicatesobre unQuery(o más arriba en la jerarquía) y tanto la memoria como el rendimiento de scroll-to-item mejoran.1 - Mueve la configuración de la vista fuera de
onAppeary hacia los inicializadores. El prefetching solo ayuda si el trabajo precargado sobrevive; una configuración que muta el tamaño o el contenido enonAppeardescarta ese trabajo.1 Esta es una ganancia silenciosa y amplia de fluidez del scroll. - Reemplaza las lecturas absolutas de offset de scroll por APIs de visibilidad relativa. Cualquier cosa ligada al offset de contenido irá a la deriva;
.onScrollTargetVisibilityChangese liga a qué filas están realmente visibles.21 - Recurre a los shaders solo donde un efecto pequeño se gane su lugar. Empieza en
colorEffectodistortionEffect; escala alayerEffectsolo cuando un efecto deba muestrear vecinos, e impulsa cualquier movimiento con una marca de tiempo de unTimelineViewen lugar de esperar estado dentro del shader.4567
El hilo conductor a través de las tres: predice el framework antes de empujarlo. Los lazy stacks estiman, los shaders olvidan, y la interoperabilidad es una costura, no una reescritura. Construye con esos tres hechos en mente y el resto se desprende.
Preguntas frecuentes
¿Por qué la posición de scroll de mi lazy stack de SwiftUI salta o va a la deriva?
Porque un LazyVStack no carga las vistas fuera de pantalla, estima sus alturas y el espacio por encima de la región visible, así que el offset absoluto de contenido es una estimación que el framework corrige a medida que aprende el layout real (después de un cambio de orientación, por ejemplo, reconcilia la estimación cuando haces scroll de vuelta hacia arriba).1 Si ligas la UI al offset absoluto, el umbral va a la deriva a medida que las estimaciones se asientan. Usa .onScrollTargetVisibilityChange, que se dispara según qué subvistas están realmente visibles, en su lugar.2
¿Cómo mantengo el scroll fluido en un lazy stack de SwiftUI en iOS 27?
Deja que el prefetching haga su trabajo: configura las vistas en su inicializador para que el trabajo que el lazy stack realiza antes de que una vista aparezca no se descarte, y evita mutar el tamaño o el contenido de una vista en onAppear.1 Además evita una cantidad dinámica de subvistas en las hojas de ForEach y evita cambios de layout (como una altura impulsada por onGeometryChange) después de que una vista aparezca, ya que ambos fuerzan al stack a rehacer trabajo o recalcular posiciones a mitad del scroll.1
¿Cuándo debería usar colorEffect, distortionEffect o layerEffect?
Usa colorEffect para transformar el color de cada píxel a partir de su posición y su color original (un filtro de escala de grises, por ejemplo).4 Usa distortionEffect para efectos geométricos, donde mapeas una posición de salida a una posición de origen desde la cual muestrear.5 Usa layerEffect cuando el píxel de salida depende de más de un píxel de entrada, porque le da al shader la capa entera de la vista para muestrear vecinos o la región completa, que es lo que necesitan el blur y el domain warping.6
¿Cómo animo un shader de Metal en SwiftUI?
Los shaders no tienen estado: no guardan memoria del frame anterior y dependen solo de sus parámetros, así que no puedes animar desde dentro del shader.3 Inyecta un valor que cambie con el tiempo. Un TimelineView en un schedule de animación se dispara cada frame con una marca de tiempo; pasa esa marca de tiempo al shader como parámetro (la sesión la suma a la posición de muestreo del ruido) y el efecto se anima.73
¿Puedo agregar SwiftUI a una app existente de AppKit o UIKit sin reescribirla?
Sí, y la sesión es explícita en que ninguna app necesita ser enteramente SwiftUI para beneficiarse.8 Marca tu modelo como @Observable para que AppKit y UIKit redibujen automáticamente, luego incrusta vistas de SwiftUI con NSHostingView, trae los gesture recognizers existentes con NSGestureRecognizerRepresentable, construye menús con NSHostingMenu, y adjunta escenas de SwiftUI desde tu app delegate con NSHostingSceneRepresentation; todos estos se incluyen en los lanzamientos de 2026 o anteriores.816171819
El cluster completo de Apple Ecosystem: el sustrato de SwiftUI (result builders, tipos opacos, el árbol de vistas tipado por valor) que explica por qué un lazy stack resuelve los structs de vista en un conjunto diferente de subvistas; la superficie de SwiftUI en iOS 27 (reordenamiento, documentos, toolbars, errores) junto a la que se sitúa esta historia de rendimiento e interoperabilidad; los internals de @Observable que ahora impulsan el redibujado automático también en AppKit y UIKit; y los patrones de Liquid Glass cuya implementación entre frameworks la sesión de interoperabilidad atribuye a SwiftUI compartido. El hub es la Serie Apple Ecosystem. Para un contexto más amplio de iOS con agentes de IA, consulta la guía de Desarrollo de Agentes en iOS.
Referencias
-
Apple, WWDC26 session 321, “Dive into lazy stacks and scrolling with SwiftUI.” developer.apple.com/videos/play/wwdc2026/321. Covers lazy-stack layout and height estimation, the estimated content offset, view-struct-to-subview resolution, the dynamic-subview-count trap, prefetching across frame deadlines, and
onAppearversus initializer setup. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple, WWDC26 session 321, “Dive into lazy stacks and scrolling with SwiftUI”. The session presents
onScrollTargetVisibilityChange(a modifier whose closure runs when the set of visible scroll targets changes) as the stable, relative-visibility alternative to absolute content-offset reads. ↩↩↩↩↩ -
Apple, WWDC26 session 322, “Compose advanced graphics effects with SwiftUI.” developer.apple.com/videos/play/wwdc2026/322. Frames effects as a composable pipeline; covers blur, the three shader entry points, the
NoiseTexturedomain-warp technique, stateless shaders driven by time, and alignment-guide attachment. ↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation:
colorEffect(_:isEnabled:). Returns a new view that applies a shader transforming each pixel’s color, given its position and original color. ↩↩↩↩ -
Apple Developer Documentation:
distortionEffect(_:maxSampleOffset:isEnabled:). Applies a shader that maps the position of each pixel to a source position to sample from, for geometric effects. ↩↩↩↩ -
Apple Developer Documentation:
layerEffect(_:maxSampleOffset:isEnabled:). Applies a shader as a layer effect with access to the entire view layer, allowing each output pixel to sample multiple input pixels. ↩↩↩↩ -
Apple Developer Documentation:
TimelineView. A view that updates its content according to a schedule; on an animation schedule it supplies the per-frame timestamp session 322 feeds into its shader. ↩↩↩↩ -
Apple, WWDC26 session 272, “Use SwiftUI with AppKit and UIKit.” developer.apple.com/videos/play/wwdc2026/272. Covers automatic
@Observableredraw in AppKit/UIKit, observation back-deployment via Info.plist,Canvas,NSHostingView,NSGestureRecognizerRepresentable,NSHostingMenu, andNSHostingSceneRepresentation. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation:
NSGestureRecognizerRepresentable. A protocol that wraps anNSGestureRecognizerfor use as a SwiftUI gesture, implemented withmakeNSGestureRecognizerandhandleNSGestureRecognizerAction. ↩ -
Apple Developer Documentation:
MenuBarExtra. A scene that renders a menu bar item; session 272 attaches it to an existing AppKit app throughNSHostingSceneRepresentation. ↩ -
Apple Developer Documentation:
scrollTransition(_:axis:transition:). Applies a transition as a view scrolls within a scroll view; session 321 warns that a transform pushing a view into the visible rect can desync a lazy stack. ↩ -
Apple Developer Documentation:
onChange(of:initial:_:). Runs an action when a value changes; session 322 uses it to recenter the current transcript line. ↩ -
Apple Developer Documentation:
alignmentGuide(_:computeValue:). Sets a view’s alignment guide so the layout system positions it semantically; session 322 overrides a bottom guide to attach a subview’s top edge to its container’s bottom edge. ↩ -
Apple Developer Documentation:
Observable. The macro that makes a class’s mutable properties participate in the Observation system, which AppKit and UIKit track for automatic redraw in the 2026 releases. ↩↩ -
Apple Developer Documentation:
Canvas. An immediate-mode drawing view whose closure receives aGraphicsContext; session 272 uses it to redraw the circular color picker and noteswithCGContextfor reusing Core Graphics code. ↩ -
Apple Developer Documentation:
NSHostingView. AnNSViewsubclass that hosts a SwiftUI view hierarchy inside an AppKit view tree. ↩↩ -
Apple Developer Documentation:
NSViewRepresentable. A wrapper that lets anNSViewparticipate in a SwiftUI view hierarchy; session 272 names it alongsideNSGestureRecognizerRepresentableas part of the representable family. ↩↩ -
Apple Developer Documentation:
NSHostingMenu. AnNSMenusubclass that renders a SwiftUI view as menu content, added to the main menu as the submenu of anNSMenuItem. ↩↩ -
Apple Developer Documentation:
keyboardShortcut(_:modifiers:). Assigns a keyboard shortcut to a control’s action; session 272 adds one to the menu button so input devices that cannot force-click still reach the feature. ↩↩ -
Apple, WWDC 2026 UI Frameworks group lab, session 8002. Paraphrased from a locally transcribed recording; no official transcript is published. The team clarified that moving code from
bodyinto a computed property is a readability change only, and that the independent-invalidation boundary appears when you extract a separate view type. ↩ -
Apple, WWDC 2026 UI Frameworks group lab, session 8003. Paraphrased from a locally transcribed recording; no official transcript is published. The team noted that every environment change invalidates all views reading that value, and advised keeping fast-changing values (the panel’s example was the current time) out of the environment. ↩
-
Apple, WWDC26 session 8006, “SwiftUI Group Lab.” developer.apple.com/videos/play/wwdc2026/8006. Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab; Apple publishes no official captions for the labs. Source for partial graph evaluation in leftover frame time (and the
onAppear-resize warning, with sizing done in init), the UIKit-top-down-versus-SwiftUI-bottom-up interop “sandwich or cake” arrangement, and the scroll-position-in-the-environment example that invalidates every reader on every frame. ↩↩↩↩↩ -
Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the
onGeometryChangetransform-gates-action mechanism (return a coarse value to fire at thresholds) and the guidance to confine an expensiveGeometryReaderto a background. The two-closure shape is documented atonGeometryChange(for:of:action:): theof:transform closure derives anEquatablevalue from the geometry proxy and theaction:closure runs only when that value changes. ↩↩ -
Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for replacing most
onChangework with a dynamic property that vends a cached value synchronously. Apple’sDynamicPropertyprotocol defines anupdate()method that SwiftUI calls immediately before rendering a view’sbodyso the property holds its most recent value. ↩↩ -
Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the culling of views that draw outside their reported layout bounds inside a
ScrollView(the failure behind overflowing custom dropdowns and overlays), and the note that the same hazard applies tooverlaycontent extending past its anchor. ↩↩ -
Apple, WWDC 2026 SwiftUI Group Lab (session 2). Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab (session 2); Apple publishes no official captions for the labs. Source for the lack of a clean pure-SwiftUI full-screen-over-sheets overlay, the drop to a new
UIWindowthrough the UIKit scene life cycle, the “last window wins / single source of truth” principle, and the preference for restoring the navigation stack over a disruptive cover. ↩↩