Esto es lo que debo hacer: tengo una vista principal con un ancho fijo y 4 hijos. 2 de los hijos (1º y 3º) tienen ancho fijo. Los otros (2º y 4º) tienen ancho flexible. La interpretación predeterminada para flexible es que compartirían equitativamente el espacio disponible, pero no quiero esto, en cambio, quiero decirle a cada Vista flexible cuánto del espacio restante debería ocupar, por ejemplo, el primero debería ocupar 2 /3 y el segundo 1/3.
Para las personas familiarizadas con Flex Layout, esto equivale a decir Quiero que mi segunda vista tenga flex: 2
y mi cuarta vista tenga flex: 1
. Vea el ejemplo a continuación:
En el ejemplo anterior, la vista principal tiene un ancho fijo de 600 px. El primer hijo tiene un ancho de 100 px, el tercero tiene 140 px. El espacio restante es de 360 px, por lo que el segundo hijo obtiene 240 px (2/3) y el cuarto hijo obtiene 120 px (1/3). Si cambio el ancho de la vista principal, el tamaño del segundo y cuarto hijos se adaptará en consecuencia.
Es extremadamente simple hacer esto para la Web, vea el siguiente código:
<div style="width: 600; display: flex; flex-direction: row;">
<div style="width: 100; height: 100; background-color: red;"></div>
<div style="flex: 2; height: 100; background-color: green;"></div>
<div style="width: 140; height: 100; background-color: blue;"></div>
<div style="flex: 1; height: 100; background-color: purple;"></div>
</div>
También es súper simple hacerlo en Flutter:
Row(
children: [
Container(height: 50, width: 50, color: Colors.red),
Flexible(flex: 2, child: Container(height: 50, color: Colors.green)),
Container(height: 50, width: 80, color: Colors.blue),
Flexible(flex: 1, child: Container(height: 50, color: Colors.purple)),
]
)
Es muy similar en Jetpack Compose (Android):
Row {
Row(modifier = Modifier.height(50.dp).width(50.dp).background(color = Color.Red)) {}
Row(modifier = Modifier.height(50.dp).weight(2F).background(color = Color.Green)) {}
Row(modifier = Modifier.height(50.dp).width(80.dp).background(color = Color.Blue)) {}
Row(modifier = Modifier.height(50.dp).weight(1F).background(color = Color.Black)) {}
}
Probé muchas cosas, pero no pude encontrar ninguna forma simple de lograr este comportamiento en SwiftUI. Según tengo entendido, para dimensionar a los niños, SwiftUI primero calcula el tamaño de cada niño cuyo tamaño puede calcularse, es decir, cada niño que tiene un tamaño fijo, luego calcula el espacio restante y lo divide por la cantidad de niños con un tamaño indefinido diciéndoles a cada uno de ellos que este es el tamaño máximo que pueden ocupar (este es el tamaño que leemos cuando usamos un GeometryReader). ¿Hay alguna manera de decirle a SwiftUI que no divida el espacio disponible por igual entre los niños con un tamaño indefinido, sino que lo divida de acuerdo con un factor especificado por cada uno de ellos?
¿Existe un enfoque diferente para esto de manera que pueda obtener el mismo resultado que las otras plataformas que mencioné aquí?
Por ahora, mi estrategia es mucho más compleja de lo que me gustaría que fuera. Presento el primer hijo con width = 50
, el segundo hijo con width = 0
(debe ser flexible), el tercer hijo con width = 80
, el cuarto hijo con width = 0
(debe ser flexible) y un quinto hijo adicional con width =.infinity
. Después del primer renderizado, mido el tamaño del quinto elemento secundario y actualizo un estado con este valor, que representa el espacio restante después de colocar los elementos secundarios con tamaño fijo. Después de actualizar el estado, puedo calcular los tamaños de los niños flexibles y renderizar todo por segunda vez, ahora con los tamaños correctos. Vea el código a continuación:
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize =.zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
struct ContentView: View {
private let totalFlexFactor = 3
@State private var freeSpace: CGSize? = nil
func getFlexibleWidth(parentWidth: CGFloat, factor: Int) -> CGFloat {
return (CGFloat(factor) / CGFloat(totalFlexFactor)) * (freeSpace?.width?? 0)
}
var body: some View {
GeometryReader { parentGeometry in
HStack(spacing: 0) {
HStack() {}
.frame(maxWidth: 50, maxHeight:.infinity)
.background(Color.red)
HStack() {}
.frame(maxWidth: getFlexibleWidth(parentWidth: parentGeometry.size.width, factor: 2), maxHeight:.infinity)
.background(Color.green)
HStack() {}
.frame(maxWidth: 80, maxHeight:.infinity)
.background(Color.blue)
HStack() {}
.frame(maxWidth: getFlexibleWidth(parentWidth: parentGeometry.size.width, factor: 1), maxHeight:.infinity)
.background(Color.purple)
if (freeSpace == nil) {
HStack() {}
.frame(maxWidth:.infinity, maxHeight:.infinity)
.background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
}
}
.frame(maxWidth:.infinity, maxHeight: 50)
.background(Color.gray)
.onPreferenceChange(SizePreferenceKey.self) { newSize in
if (freeSpace == nil) {
freeSpace = newSize
}
}
}
}
}
Créditos a Federico Zanetello por el código que mide el tamaño de los niños.
Como dije antes, no veo esto como una solución muy elegante, sobre todo si lo comparamos con las otras plataformas. Agradecería cualquier sugerencia! Solo tenga en cuenta el ejemplo que di: vistas de tamaño fijo intercaladas con vistas de tamaño flexible.
Solución del problema
Si bien creo que PreferenceKeys
son una gran herramienta para tener y usar, este no es un caso en el que sean necesarios. Tu plan era demasiado complicado. Esto se reduce a un simple problema matemático, pero primero hay un problema de codificación:
.frame(maxWidth: 50, maxHeight:.infinity)
le da un ancho flexible, no fijo. Lo que ha dicho ViewBuilder
que haga es asignar al ancho del marco cualquier valor hasta 50, no un valor de 50. Entiendo lo que estaba haciendo, ya que quería expandir la altura al tamaño del contenedor de vista. Hay dos alternativas:
.frame(minWidth: 50, maxWidth: 50, maxHeight:.infinity)
o
.frame(width: 50)
.frame(maxHeight:.infinity)
Elegí este último porque deja muy clara tu intención. Como eran fijos, también los asigné a let
constantes para usarlos en cualquier lugar que los necesitara.
Además, aunque no probé esto, sospecho PreferenceKey
que no pudo manejar la vista de desaparición/reaparición. Como eran innecesarios, no profundizó en ello.
En este punto, puede encontrar su espacio flexible simplemente parentGeometry
reduciendo los dos anchos fijos. No PreferenceKeys
es necesario. al final, el código se simplifica a:
struct ContentView: View {
private let totalFlexFactor: CGFloat = 3
@State private var freeSpace: CGSize? = nil
private let width1: CGFloat = 100
private let width2: CGFloat = 80
private func getFlexibleWidth(parentWidth: CGFloat, factor: CGFloat) -> CGFloat {
((parentWidth - (width1 + width2)) / totalFlexFactor) * factor
}
var body: some View {
GeometryReader { parentGeometry in
HStack(spacing: 0) {
HStack {
Text(width1.description)
}
.frame(width: width1)
.frame(maxHeight:.infinity)
.background(Color.red)
HStack {
Text(getFlexibleWidth(parentWidth: parentGeometry.size.width, factor: 2).description)
}
.frame(maxWidth: getFlexibleWidth(parentWidth: parentGeometry.size.width, factor: 2), maxHeight:.infinity)
.background(Color.green)
HStack {
Text(width2.description)
}
.frame(width: width2)
.frame(maxHeight:.infinity)
.background(Color.blue)
HStack {
Text(getFlexibleWidth(parentWidth: parentGeometry.size.width, factor: 1).description)
}
.frame(maxWidth: getFlexibleWidth(parentWidth: parentGeometry.size.width, factor: 1), maxHeight:.infinity)
.background(Color.purple)
}
.frame(maxWidth:.infinity, maxHeight: 50)
.background(Color.gray)
}
}
}
También mostré los valores para los anchos de varios HStacks
en el archivo HStacks
.
Además, con respecto a la sintaxis de SwiftUI, no es necesario escribir a HStack()
menos que tenga la intención de usar los valores opcionales de alignment:
y spacing:
, de lo contrario, omitiremos el ().
No hay comentarios:
Publicar un comentario