Digamos que he agregado más vistas en UIStackView que se pueden mostrar, ¿cómo puedo hacer el UIStackView
desplazamiento?
Digamos que he agregado más vistas en UIStackView que se pueden mostrar, ¿cómo puedo hacer el UIStackView
desplazamiento?
Respuestas:
En caso de que alguien esté buscando una solución sin código , creé un ejemplo para hacerlo completamente en el guión gráfico, usando el diseño automático.
Puedes obtenerlo de github .
Básicamente, para recrear el ejemplo (para desplazamiento vertical):
UIScrollView
y establezca sus restricciones.UIStackView
aUIScrollView
Leading
, Trailing
, Top
y Bottom
debe ser igual a los deUIScrollView
Width
restricción igual entre UIStackView
y UIScrollView
.UIStackView
UIViews
laUIStackView
Intercambie Width
por Height
en el paso 4, y establezca Axis
= Horizontal
en el paso 5, para obtener un UIStackView horizontal.
La Guía de diseño automático de Apple incluye una sección completa sobre Trabajar con vistas de desplazamiento . Algunos fragmentos relevantes:
- Ancle los bordes superior, inferior, inicial y posterior de la vista de contenido a los bordes correspondientes de la vista de desplazamiento. La vista de contenido ahora define el área de contenido de la vista de desplazamiento.
- (Opcional) Para deshabilitar el desplazamiento horizontal, configure el ancho de la vista de contenido igual al ancho de la vista de desplazamiento. La vista de contenido ahora llena la vista de desplazamiento horizontalmente.
- (Opcional) Para deshabilitar el desplazamiento vertical, configure la altura de la vista de contenido igual a la altura de la vista de desplazamiento. La vista de contenido ahora llena la vista de desplazamiento horizontalmente.
Además:
Su diseño debe definir completamente el tamaño de la vista de contenido (excepto donde se define en los pasos 5 y 6). ... Cuando la vista de contenido es más alta que la vista de desplazamiento, la vista de desplazamiento permite el desplazamiento vertical. Cuando la vista de contenido es más amplia que la vista de desplazamiento, la vista de desplazamiento permite el desplazamiento horizontal.
Para resumir, la vista de contenido de la vista de desplazamiento (en este caso, una vista de pila) debe estar anclada a sus bordes y su ancho y / o altura deben estar restringidos. Eso significa que el contenido de la vista de la pila debe estar restringido (directa o indirectamente) en la dirección o direcciones en las que se desea el desplazamiento, lo que podría significar agregar una restricción de altura a cada vista dentro de una vista de la pila de desplazamiento vertical, por ejemplo. El siguiente es un ejemplo de cómo permitir el desplazamiento vertical de una vista de desplazamiento que contiene una vista de pila:
// Pin the edges of the stack view to the edges of the scroll view that contains it
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// Set the width of the stack view to the width of the scroll view for vertical scrolling
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
Need constraints for: Y position or height.
este error solo desaparece si establece una restricción de ancho y altura para la vista de la pila, que deshabilita el desplazamiento vertical. ¿Este código todavía funciona para ti?
Las restricciones en la respuesta mejor votada aquí funcionaron para mí, y he pegado una imagen de las restricciones a continuación, tal como se creó en mi guión gráfico.
Sin embargo, encontré dos problemas que otros deberían tener en cuenta:
Después de agregar restricciones similares a las que figuran en la respuesta aceptada, obtendría el error rojo de auto-distribución Need constraints for: X position or width
. Esto se resolvió agregando un UILabel como una subvista de la vista de pila.
Estoy agregando las subvistas programáticamente, por lo que originalmente no tenía subvistas en el guión gráfico. Para deshacerse de los errores de distribución automática, agregue una subvista al guión gráfico, luego quítelo en carga antes de agregar sus subvistas y restricciones reales.
Originalmente intenté agregar UIButtons
a UIStackView. Los botones y las vistas se cargarían, pero la vista de desplazamiento no se desplazaría . Esto se resolvió agregando UILabels
a la Vista de pila en lugar de botones. Usando las mismas restricciones, esta jerarquía de vistas con UILabels se desplaza pero UIButtons no.
Estoy confundido por este problema, ya que los UIButtons no parecen tener un IntrinsicContentSize (utilizado por la pila Vista). Si alguien sabe por qué los botones no funcionan, me encantaría saber por qué.
Aquí está mi jerarquía de vista y restricciones, para referencia:
Como dice Eik, UIStackView
y UIScrollView
juegan juntos muy bien, mira aquí .
La clave es que UIStackView
maneja la altura / ancho variable para diferentes contenidos y UIScrollView
luego hace bien su trabajo de desplazamiento / rebote de ese contenido:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)
}
Estaba buscando hacer lo mismo y me topé con esta excelente publicación. Si desea hacer esto mediante programación utilizando la API de anclaje, este es el camino a seguir.
Para resumir, incrusta tu UIStackView
en tu UIScrollView
y establece las restricciones de anclaje de la UIStackView
para que coincidan con las de la UIScrollView
:
stackView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor).active = true
stackView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor).active = true
stackView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor).active = true
stackView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor).active = true
stackView.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
Tener una escena en pantalla completa en blanco
Agregar una vista de desplazamiento. Control-arrastre desde la vista de desplazamiento a la vista base , agregue izquierda-derecha-arriba-abajo, todo cero.
Agregue una vista de pila en la vista de desplazamiento. Control-arrastre desde la vista de pila a la vista de desplazamiento , agregue izquierda-derecha-arriba-abajo, todo cero.
Ponga dos o tres etiquetas dentro de la vista de pila.
Para mayor claridad, haga que el color de fondo de la etiqueta sea rojo. Establezca la altura de la etiqueta en 100.
Ahora configure el ancho de cada UILabel:
Sorprendentemente, arrastre el control de la UILabel
vista de desplazamiento a la vista de desplazamiento, no a la vista de pila, y seleccione anchos iguales .
Repetir:
Es así de simple. Ese es el secreto
Consejo secreto: error de Apple:
¡ No funcionará con un solo artículo! Agregue algunas etiquetas para que la demostración funcione.
Ya terminaste
Consejo: debe agregar una altura a cada elemento nuevo . Cada elemento en cualquier vista de pila de desplazamiento debe tener un tamaño intrínseco (como una etiqueta) o agregar una restricción de altura explícita.
El enfoque alternativo:
En lo anterior: sorprendentemente, establezca los anchos de las UILabels al ancho de la vista de desplazamiento (no la vista de la pila).
Alternativamente...
Así que tienes dos opciones:
o
Para ser claros, haga UNO de esos métodos, NO haga los dos.
Para desplazamiento horizontal. Primero, cree ay UIStackView
a UIScrollView
y agréguelos a su vista de la siguiente manera:
let scrollView = UIScrollView()
let stackView = UIStackView()
scrollView.addSubview(stackView)
view.addSubview(scrollView)
Recordando a establecer la translatesAutoresizingMaskIntoConstraints
a false
en el UIStackView
y el UIScrollView
:
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
Para que todo funcione, los anclajes posteriores, superiores e inferiores UIStackView
deben ser iguales a los UIScrollView
anclajes:
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
Pero el ancho del anclaje UIStackView
debe ser igual o mayor que el ancho del UIScrollView
anclaje:
stackView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor).isActive = true
Ahora ancla tu UIScrollView
, por ejemplo:
scrollView.heightAnchor.constraint(equalToConstant: 80).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo:view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo:view.trailingAnchor).isActive = true
A continuación, sugeriría probar las siguientes configuraciones para la UIStackView
alineación y distribución:
topicStackView.axis = .horizontal
topicStackView.distribution = .equalCentering
topicStackView.alignment = .center
topicStackView.spacing = 10
Finalmente, deberá usar el addArrangedSubview:
método para agregar subvistas a su UIStackView.
Una característica adicional que puede resultarle útil es que, dado que se UIStackView
encuentra dentro de una UIScrollView
, ahora tiene acceso a inserciones de texto para que las cosas se vean un poco más bonitas.
let inset:CGFloat = 20
scrollView.contentInset.left = inset
scrollView.contentInset.right = inset
// remember if you're using insets then reduce the width of your stack view to match
stackView.widthAnchor.constraint(greaterThanOrEqualTo: topicScrollView.widthAnchor, constant: -inset*2).isActive = true
Solo agregue esto a viewdidload
:
let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
scrollVIew.contentInset = insets
scrollVIew.scrollIndicatorInsets = insets
Puede probar ScrollableStackView : https://github.com/gurhub/ScrollableStackView
Es una biblioteca compatible con Objective-C y Swift. Está disponible a través de CocoaPods .
Código de muestra (Swift)
import ScrollableStackView
var scrollable = ScrollableStackView(frame: view.frame)
view.addSubview(scrollable)
// add your views with
let rectangle = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 55))
rectangle.backgroundColor = UIColor.blue
scrollable.stackView.addArrangedSubview(rectangle)
// ...
Código de muestra (Objetivo-C)
@import ScrollableStackView
ScrollableStackView *scrollable = [[ScrollableStackView alloc] initWithFrame:self.view.frame];
scrollable.stackView.distribution = UIStackViewDistributionFillProportionally;
scrollable.stackView.alignment = UIStackViewAlignmentCenter;
scrollable.stackView.axis = UILayoutConstraintAxisVertical;
[self.view addSubview:scrollable];
UIView *rectangle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 55)];
[rectangle setBackgroundColor:[UIColor blueColor]];
// add your views with
[scrollable.stackView addArrangedSubview:rectangle];
// ...
Si tiene una restricción para centrar la Vista de pila verticalmente dentro de la vista de desplazamiento, simplemente quítela.
Ejemplo para una vista vertical de pila / vista de desplazamiento (usando EasyPeasy
para autolayout):
let scrollView = UIScrollView()
self.view.addSubview(scrollView)
scrollView <- [
Edges(),
Width().like(self.view)
]
let stackView = UIStackView(arrangedSubviews: yourSubviews)
stackView.axis = .vertical
stackView.distribution = .fill
stackView.spacing = 10
scrollView.addSubview(stackView)
stackView <- [
Edges(),
Width().like(self.view)
]
¡Solo asegúrese de que cada una de las alturas de su subvista esté definida!
En primer lugar, diseñe su vista, preferiblemente en algo como Sketch u obtenga una idea de lo que desea como contenido desplazable.
Después de esto, haga que el controlador de vista tenga forma libre (elija del inspector de atributos) y establezca la altura y el ancho según el tamaño del contenido intrínseco de su vista (a elegir del inspector de tamaño).
Después de esto, en el controlador de vista, coloque una vista de desplazamiento y esta es una lógica, que he encontrado que funciona casi todas las veces en iOS (puede requerir revisar la documentación de esa clase de vista que se puede obtener mediante comando + clic en esa clase o en google)
Si está trabajando con dos o más vistas, primero comience con una vista que se haya introducido anteriormente o sea más primitiva y luego vaya a la vista que se introdujo más tarde o que es más moderna. Entonces, como la vista de desplazamiento se introdujo primero, comience con la vista de desplazamiento primero y luego vaya a la vista de pila. Aquí ponga las restricciones de la vista de desplazamiento a cero en todas las direcciones con respecto a su super vista. Coloque todas sus vistas dentro de esta vista de desplazamiento y luego póngalas en la vista de pila.
Mientras trabaja con la vista de pila
Primero comience con ground up (enfoque de abajo hacia arriba), es decir, si tiene etiquetas, campos de texto e imágenes en su vista, luego presente estas vistas primero (dentro de la vista de desplazamiento) y luego póngalas en la vista de pila.
Después de eso, modifique la propiedad de la vista de pila. Si aún no se logra la vista deseada, utilice otra vista de pila.
Después de hacer su vista, coloque una restricción entre la vista de la pila madre y la vista de desplazamiento, mientras que la vista de la pila secundaria de los niños con la vista de la pila madre. Con suerte, para este momento debería funcionar bien o puede recibir una advertencia de Xcode dando sugerencias, lea lo que dice e implemente esas. Con suerte, ahora debería tener una visión funcional según sus expectativas :).
Para la vista de pila apilada o anidada, la vista de desplazamiento debe tener un ancho fijo con la vista raíz. La vista de la pila principal que está dentro de la vista de desplazamiento debe establecer el mismo ancho. [Mi vista de desplazamiento está debajo de una vista ignorarlo]
Configure una restricción de ancho igual entre UIStackView y UIScrollView.
Si alguien busca una vista de desplazamiento horizontal
func createHorizontalStackViewsWithScroll() {
self.view.addSubview(stackScrollView)
stackScrollView.translatesAutoresizingMaskIntoConstraints = false
stackScrollView.heightAnchor.constraint(equalToConstant: 85).isActive = true
stackScrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
stackScrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
stackScrollView.bottomAnchor.constraint(equalTo: visualEffectViews.topAnchor).isActive = true
stackScrollView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: stackScrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: stackScrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: stackScrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: stackScrollView.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: stackScrollView.heightAnchor).isActive = true
stackView.distribution = .equalSpacing
stackView.spacing = 5
stackView.axis = .horizontal
stackView.alignment = .fill
for i in 0 ..< images.count {
let photoView = UIButton.init(frame: CGRect(x: 0, y: 0, width: 85, height: 85))
// set button image
photoView.translatesAutoresizingMaskIntoConstraints = false
photoView.heightAnchor.constraint(equalToConstant: photoView.frame.height).isActive = true
photoView.widthAnchor.constraint(equalToConstant: photoView.frame.width).isActive = true
stackView.addArrangedSubview(photoView)
}
stackView.setNeedsLayout()
}
Coloque una vista de desplazamiento en su escena y modifíquela para que llene la escena. Luego, coloque una vista de pila dentro de la vista de desplazamiento y coloque el botón Agregar elemento dentro de la vista de pila. Tan pronto como todo esté en su lugar, establezca las siguientes restricciones:
Scroll View.Leading = Superview.LeadingMargin
Scroll View.Trailing = Superview.TrailingMargin
Scroll View.Top = Superview.TopMargin
Bottom Layout Guide.Top = Scroll View.Bottom + 20.0
Stack View.Leading = Scroll View.Leading
Stack View.Trailing = Scroll View.Trailing
Stack View.Top = Scroll View.Top
Stack View.Bottom = Scroll View.Bottom
Stack View.Width = Scroll View.Width
código: Stack View.Width = Scroll View.Width
es la clave.
Agregar una nueva perspectiva para macOS Catalyst. Dado que las aplicaciones macOS admiten el cambio de tamaño de la ventana, es posible que su UIStackView
transición de un estado no desplazable a uno desplazable, o viceversa. Aquí hay dos cosas sutiles:
UIStackView
está diseñado para adaptarse a todas las áreas que puede.UIScrollView
intentará cambiar el tamaño de sus límites para tener en cuenta el área recién adquirida / perdida debajo de su barra de navegación (o barra de herramientas en el caso de las aplicaciones de macOS).Desafortunadamente, esto creará un bucle infinito. No estoy muy familiarizado con UIScrollView
su y adjustedContentInset
, pero desde mi inicio de sesión en su layoutSubviews
método, veo el siguiente comportamiento:
UIScrollView
intenta reducir sus límites (ya que no es necesario el área debajo de la barra de herramientas).UIStackView
sigue.UIScrollView
no está satisfecho, y decide restaurar a los límites más grandes. Esto me parece muy extraño ya que lo que veo en el registro es eso UIScrollView.bounds.height == UIStackView.bounds.height
.UIStackView
sigue.Me parece que dos pasos solucionarían el problema:
UIStackView.top
a UIScrollView.topMargin
.contentInsetAdjustmentBehavior
a .never
.Aquí me preocupa una vista desplazable verticalmente con un crecimiento vertical UIStackView
. Para un par horizontal, cambie el código en consecuencia.
Espero que ayude a cualquiera en el futuro. No pude encontrar a nadie que mencionara esto en Internet y me costó mucho tiempo descubrir qué sucedió.