Para corregir el contenido de SwiftUI que se desplaza fuera de la pantalla al cambiar el tamaño de una ventana en Mac, primero identifica si el dibujo rebasa los límites de su diseño, el contenido se resiste a reducirse o una vista de AppKit integrada deja de seguir a su contenedor. Luego corrige ese límite específico.
Esta guía está dirigida a desarrolladores que corrigen la interfaz redimensionable de su propia app para Mac: mantener las etiquetas legibles, los controles accesibles y el contenido extenso desplazable. La secuencia de diagnóstico que sigue utiliza etiquetas sintéticas y distingue entre el diseño de SwiftUI, el tamaño de la ventana y el ajuste de línea de texto de AppKit.
Identifica qué límite falla al cambiar el tamaño
El diseño de SwiftUI comienza con una propuesta de tamaño del elemento principal. Un elemento secundario elige su tamaño en respuesta; la propuesta no es una instrucción para recortar todo a ese rectángulo. Apple documenta las propuestas de tamaño mínimo, máximo e ideal en ProposedViewSize. Esto explica por qué estrechar una ventana no necesariamente hace que todos sus elementos descendientes se estrechen.
Empieza con una prueba reproducible: abre la ventana de tu app, estrechala hasta que aparezca el primer fallo y luego vuelve a ensancharla. Observa el primer elemento afectado, no la disposición final. ¿Una etiqueta sobresale de su contenedor? ¿Se desplaza todo el panel? ¿El texto conserva el ancho de ajuste de línea anterior?
Añade temporalmente .border(Color.orange) al elemento secundario sospechoso y .border(Color.blue) al marco o contenedor que lo rodea. Coloca los modificadores en distintos niveles para poder comparar sus rectángulos. La documentación de offset de Apple muestra esta técnica del borde para distinguir la posición original del diseño de un dibujo desplazado.
| Lo que observas | Qué inspeccionar primero | Siguiente acción útil |
|---|---|---|
| El dibujo se sale de un rectángulo que, por lo demás, permanece estable | offset, position, cálculos de coordenadas |
Quita los modificadores de posición uno por uno |
| Una etiqueta conserva su ancho mientras se estrecha el contenedor | fixedSize horizontal, anchos explícitos |
Permite que la etiqueta acepte un ancho restringido |
| Los controles no caben en una fila horizontal | Anchos combinados de los controles y espaciado | Ofrece una disposición vertical alternativa |
| El contenido inferior desaparece al reducirse la altura | Falta un comportamiento de desbordamiento | Permite desplazar el área de contenido |
| Un panel de SwiftUI alojado se desplaza dentro de AppKit | Límites y restricciones de tamaño de la vista de alojamiento | Compara el tamaño del contenedor con los requisitos del contenido |
| Una vista de texto de AppKit se estrecha, pero las líneas siguen siendo anchas | Seguimiento del ancho del contenedor de texto | Comprueba la configuración de la vista de texto y del contenedor de texto |
Estas son hipótesis de diagnóstico derivadas de los comportamientos documentados, no una garantía de que un síntoma concreto tenga una sola causa. Cambia una sola cosa sospechosa cada vez y repite el mismo gesto de redimensionamiento. Si reemplazar un elemento secundario complejo por Text("Sample panel") elimina el fallo, vuelve a añadir gradualmente las capas de ese elemento.
Quita las reglas de posición y tamaño que se resisten a la ventana
Comprueba los desplazamientos y el posicionamiento basado en el centro
Un offset desplaza el contenido mostrado sin cambiar las dimensiones originales de la vista. Por eso, un desplazamiento horizontal positivo puede empujar el dibujo más allá del borde derecho, aunque el elemento principal siga reservando el espacio original. Quita temporalmente el desplazamiento; si el dibujo vuelve al interior del contenedor, sustituye ese ajuste por una disposición expresada mediante pilas, relleno o alineación del marco. Consulta el comportamiento de offset de Apple.
position(x:y:) coloca el centro de la vista en unas coordenadas del espacio de coordenadas de su elemento principal. Interpretar esas coordenadas como la esquina superior izquierda introduce un error relacionado con el tamaño del elemento secundario. Además, una posición calculada una sola vez puede dejar de ser adecuada cuando cambia el tamaño del elemento principal. Para un panel habitual de etiquetas y controles, prefiere una alineación que exprese la relación deseada con el contenedor. Apple define el comportamiento basado en el centro en position(x:y:).
Por ejemplo, .frame(maxWidth: .infinity, alignment: .leading) expresa la alineación inicial dentro de un marco flexible. No elimina los requisitos de tamaño propios de un elemento descendiente demasiado grande. La documentación de los marcos flexibles de Apple explica cómo el marco restringe las propuestas y posiciona su elemento secundario.
Comprueba los anchos fijos y el tamaño ideal del texto
Inspecciona los ajustes de ancho explícitos y los usos horizontales de fixedSize en la rama que falla. Apple documenta específicamente que .fixedSize(horizontal: true, vertical: false) hace que el texto conserve el ancho ideal necesario para la cadena completa, lo que puede superar los límites de su elemento principal. Para el texto del cuerpo que debe ajustarse en varias líneas, quita ese requisito horizontal. Cuando sea adecuado conservar toda la altura del texto ajustado, considera .fixedSize(horizontal: false, vertical: true). Consulta fixedSize(horizontal:vertical:).
Conservar el tamaño vertical requiere que haya espacio para esa altura. Un párrafo que crece verticalmente dentro de un contenedor bajo sin desplazamiento puede seguir quedando inaccesible. Usa una región de desplazamiento vertical cuando la tarea requiera acceder a todo el texto; Apple documenta los ejes de desplazamiento admitidos en ScrollView.
Vuelve a comprobar los cálculos basados en la geometría
GeometryReader define el contenido usando su propio tamaño y espacio de coordenadas, y devuelve a su elemento principal un tamaño preferido flexible. Añadir uno alrededor de una vista cambia la estructura del diseño, así que no es un envoltorio de medición neutro. Si el desplazamiento empezó después de introducir cálculos geométricos, sustituye temporalmente esa rama por pilas y marcos normales. Apple describe estas propiedades en GeometryReader.
Para una disposición proporcional que realmente necesite geometría, deriva las dimensiones del contenedor actual pertinente. Inspecciona cualquier estado que almacene un ancho capturado durante la aparición inicial. Comprueba también si la dimensión medida se usa para redimensionar la misma región que se está midiendo; eliminar esa dependencia es una prueba de aislamiento útil.
Crea un diseño compacto con un comportamiento de desbordamiento explícito
El siguiente ejemplo completo muestra tres decisiones independientes: el texto se ajusta en varias líneas, los controles cambian de disposición cuando es necesario y el contenido que desborda en vertical sigue siendo accesible. Sus dimensiones son valores de diseño ilustrativos, no valores prescritos por Apple.
Crea una app SwiftUI para macOS en Xcode, establece el destino de implementación en macOS 13 o posterior y sustituye las declaraciones generadas de la app y la vista de contenido por este único archivo. Conserva una sola declaración @main. El ejemplo usa las API ViewThatFits y de redimensionamiento de ventanas disponibles en macOS 13. Apple documenta su comportamiento en ViewThatFits y windowResizability(_:).
import SwiftUI
@main
struct ResizeExampleApp: App {
var body: some Scene {
WindowGroup {
ResizeExampleView()
.frame(minWidth: 280, minHeight: 220)
}
.windowResizability(.contentMinSize)
}
}
struct ResizeExampleView: View {
@State private var selection = "Sample A"
private let details = Array(
repeating: "Sample notes wrap as the panel becomes narrower.",
count: 12
).joined(separator: " ")
var body: some View {
ScrollView(.vertical) {
VStack(alignment: .leading, spacing: 16) {
Text("Sample panel")
.font(.title2)
Text(details)
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
ViewThatFits(in: .horizontal) {
HStack(spacing: 12) {
Button("Choose Sample A") {
selection = "Sample A"
}
Button("Choose Sample B") {
selection = "Sample B"
}
}
.fixedSize(horizontal: true, vertical: false)
VStack(alignment: .leading, spacing: 12) {
Button("Choose Sample A") {
selection = "Sample A"
}
Button("Choose Sample B") {
selection = "Sample B"
}
}
}
Text("Selected: \(selection)")
}
.padding(20)
.frame(
minWidth: 0,
maxWidth: .infinity,
alignment: .leading
)
}
}
}
ViewThatFits evalúa las alternativas en el orden declarado y elige la primera cuyo tamaño ideal cabe en los ejes restringidos. Aquí, el ajuste horizontal selecciona entre la fila de botones y la pila vertical. La fila conserva deliberadamente su tamaño ideal horizontal para que una fila comprimida no se convierta en el resultado preferido. Mantén las dos alternativas equivalentes en cuanto a las acciones disponibles. Consulta las reglas de selección de Apple.
El marco exterior permite que la columna de contenido siga el ancho disponible, mientras que la vista de desplazamiento vertical permite acceder al contenido cuando el ajuste de línea aumenta la altura. Esta disposición supone que los controles individuales caben en el ancho mínimo elegido. Las etiquetas más largas o las fuentes más grandes pueden requerir otro mínimo o una disposición compacta distinta.
Se revisó este ejemplo para comprobar la coherencia de las declaraciones y la disponibilidad de las API; no se compiló aquí porque no había un compilador de Swift disponible. Ejecútalo en macOS para verificar el comportamiento real de la ventana y el texto.
Establece los límites de la ventana después de corregir el diseño del contenido
Un tamaño mínimo de ventana adecuado impide que la interfaz sea más pequeña que la disposición que admite. Con .contentMinSize, SwiftUI deriva el tamaño mínimo de la ventana de su contenido y no impone un tamaño máximo. En el ejemplo, el marco raíz proporciona los valores de ancho y alto mínimos. Consulta contentMinSize.
Elige ese mínimo probando la disposición compacta con las etiquetas admitidas más largas. Aumentar el mínimo hasta que una vista mal ubicada se vuelva visible puede ocultar el error de diseño original. Primero elimina los desplazamientos involuntarios y los elementos secundarios demasiado grandes; después establece las dimensiones mínimas con las que la tarea prevista siga siendo utilizable.
Si AppKit controla la ventana y aloja SwiftUI, inspecciona por separado el límite de alojamiento. En macOS 13 y versiones posteriores, NSHostingView.sizingOptions controla las restricciones derivadas del contenido de SwiftUI. Cuando la vista de alojamiento es la contentView de la ventana, también actualiza los tamaños mínimo y máximo del contenido de la ventana. Apple también documenta que el contenido se centra cuando el marco de la vista de alojamiento difiere del tamaño requerido por su contenido de SwiftUI. Comprueba estas relaciones antes de añadir desplazamientos compensatorios. Consulta NSHostingView sizingOptions.
Comprueba AppKit y TextKit solo si la vista que falla los utiliza
Deja que SwiftUI dimensione la vista raíz representada
En NSViewRepresentable, SwiftUI controla frame y bounds de la vista de AppKit representada. Apple advierte explícitamente que establecer esas propiedades por tu cuenta entra en conflicto con SwiftUI y produce un comportamiento indefinido. Si tu controlador de redimensionamiento asigna repetidamente el marco de la vista raíz representada, elimina esa gestión en conflicto y expresa el tamaño a través del límite de SwiftUI. Consulta NSViewRepresentable.
Cuando sea necesario medir de forma personalizada, macOS 13 y versiones posteriores proporciona sizeThatFits(_:nsView:context:). Puede recibir varias propuestas durante una sola pasada de diseño; devolver nil solicita el dimensionamiento predeterminado. Haz que el resultado dependa de la propuesta actual y del contenido, en lugar de un ancho anterior de la ventana. Una dimensión propuesta sin especificar necesita una política de medición deliberada, en vez de tratarse automáticamente como cero. Consulta el dimensionamiento de vistas representables.
El enlace opcional de Swift permite comprobar y extraer una dimensión propuesta antes de usarla. Esto resulta útil al implementar una política de dimensionamiento sin desenvolver a la fuerza los anchos o alturas opcionales. El comportamiento del lenguaje se documenta en The Basics: Optional Binding.
Separa el ancho de ajuste de línea del ancho de la vista de texto
Si el elemento secundario que falla es un NSTextView, compara su ancho actual con el ancho de su contenedor de texto. Apple documenta que NSTextContainer.widthTracksTextView tiene el valor predeterminado false; establecerlo en true hace que el contenedor ajuste su ancho cuando cambia el ancho de la vista de texto. Ese es el control pertinente cuando las líneas deben redistribuirse según el ancho de la vista de texto. Consulta widthTracksTextView.
Inspecciona también isHorizontallyResizable. Controla si la vista de texto cambia de ancho para ajustarse a su texto, un requisito distinto del ajuste del texto dentro de un ancho asignado. Para una región de texto con ajuste de línea y desplazamiento vertical, configura ambas políticas de manera coherente y asegúrate de que el diseño contenedor realmente redimensione la vista de texto. Consulta isHorizontallyResizable.
Conserva el motor de texto que use tu implementación. Apple documenta que acceder a layoutManager en un NSTextView o su contenedor de texto puede cambiarlo al modo de compatibilidad, que utiliza NSLayoutManager. No añadas ese acceso a la ligera al diagnosticar una vista de TextKit 2. Usa las API adecuadas para el motor existente y el destino de implementación. Consulta NSTextView.
Verifica la corrección tras cambiar el ancho y la altura
Sigue esta secuencia final en tu propia app:
- Estrecha la ventana lentamente hasta pasar el punto en que fallaba originalmente y luego vuelve a ensancharla.
- Reduce la altura por separado y confirma que todo el contenido necesario siga siendo accesible.
- Repite la prueba con etiquetas sintéticas largas y texto en varias líneas.
- Activa cada control en las disposiciones ancha y compacta.
- En una vista de texto de AppKit, confirma que el ajuste de línea siga el ancho actual en ambas direcciones de redimensionamiento.
Una corrección satisfactoria mantiene la posición vinculada al contenedor actual, ofrece al contenido una disposición compacta funcional y permite acceder al contenido que desborda. Cuando esos comportamientos se mantengan al repetir el redimensionamiento, quita los bordes temporales y conserva el tamaño mínimo de ventana que justifique la interfaz.
Fuentes
- ProposedViewSize — Apple
- offset(x:y:) — Apple
- position(x:y:) — Apple
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple
- fixedSize(horizontal:vertical:) — Apple
- ScrollView — Apple
- GeometryReader — Apple
- ViewThatFits — Apple
- windowResizability(_:) — Apple
- contentMinSize — Apple
- sizingOptions — Apple
- NSViewRepresentable — Apple
- sizeThatFits(_:nsView:context:) — Apple
- The Basics — Swift.org
- widthTracksTextView — Apple
- isHorizontallyResizable — Apple
- NSTextView — Apple