¿Es posible que UIStackView se desplace?


210

Digamos que he agregado más vistas en UIStackView que se pueden mostrar, ¿cómo puedo hacer el UIStackViewdesplazamiento?


¿Por qué no está utilizando una vista de desplazamiento con una (o varias) vistas de pila para el contenido?
Wain

2
La solución de una frase es que para el ancho que controlas, arrastra a la vista de desplazamiento de los abuelos, NO a la vista de pila principal. ¡Explicado en mi respuesta!
Fattie

Cómo incrustar UIStackView dentro de UIScrollView en iOS github.com/onmyway133/blog/issues/324
onmyway133

En cuanto a esta pregunta crítica en iOS. De hecho, hay dos (y solo dos) formas de hacerlo : esa es la única forma de hacerlo y debe seguir uno de los dos procedimientos, exactamente. Mi respuesta a continuación siempre se actualiza.
Fattie

SI. DEBE tener una UIView intermedia como vista de contenido de UIScrollView. Luego coloque UIStackView como vista secundaria de la vista de contenido. raywenderlich.com/…
poGUIst

Respuestas:


578

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):

  1. Cree un UIScrollViewy establezca sus restricciones.
  2. Agregue un UIStackViewaUIScrollView
  3. Establecer las limitaciones: Leading, Trailing, Topy Bottomdebe ser igual a los deUIScrollView
  4. Configure una Widthrestricción igual entre UIStackViewy UIScrollView.
  5. Establecer eje = vertical, alineación = relleno, distribución = espaciado igual y espaciado = 0 en el UIStackView
  6. Añadir un número de UIViewslaUIStackView
  7. correr

Intercambie Widthpor Heighten el paso 4, y establezca Axis= Horizontalen el paso 5, para obtener un UIStackView horizontal.


55
¡Gracias! "4. Configure una restricción de ancho igual entre UIStackView y UIScrollView". era la clave;)
blanco

19
El número 6. también es importante. Deseaba agregar todas las vistas en el código, pero luego el Diseño automático muestra las restricciones faltantes en IB.
antes del

11
Hola, parece que esta solución no funciona para el desplazamiento horizontal. En el paso 4, en lugar de establecer el mismo ancho, supongo que establecer la misma altura, y Axis = Horizontal. Lo intenté y no funciona.
Seto Elkahfi

55
Agregar una restricción para que la vista de pila y la vista de desplazamiento tengan anchos iguales eliminará las advertencias del generador de interfaces, pero luego se deshabilitará el desplazamiento. ¡El desplazamiento solo ocurre cuando la vista de contenido dentro de la vista de desplazamiento es más grande que la vista de desplazamiento!
Jason Moore

66
Para elaborar sobre el punto 6. Si la vista de la pila no tenía elementos pero todas las restricciones están en su lugar, se obtiene "La vista de desplazamiento necesita restricciones para la posición Y altura". ¡Solo agregar una etiqueta en la vista de pila hace que esta advertencia desaparezca!
Faisal Memon

45

La Guía de diseño automático de Apple incluye una sección completa sobre Trabajar con vistas de desplazamiento . Algunos fragmentos relevantes:

  1. 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.
  2. (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.
  3. (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

1
Gracias, pero omitir la restricción de altura (para permitir el desplazamiento vertical) produce este error en Storyboard: 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?
Crashalot

@Crashalot Para eliminar ese error, debe agregar una subvista a la vista de pila; mira mi respuesta a continuación.
pkamb

17

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:

  1. 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.

  2. Originalmente intenté agregar UIButtonsa UIStackView. Los botones y las vistas se cargarían, pero la vista de desplazamiento no se desplazaría . Esto se resolvió agregando UILabelsa 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:

restricciones para la vista de pila en la vista de desplazamiento [1]


3
me ahorraste un montón de horas de golpe de cabeza contra la pared y frustración, estoy exactamente en la misma situación y no pude entender la razón de la advertencia y por qué no funciona con botones. ¿Por qué Apple por qué?
PakitoV

15

Como dice Eik, UIStackViewy UIScrollViewjuegan juntos muy bien, mira aquí .

La clave es que UIStackViewmaneja la altura / ancho variable para diferentes contenidos y UIScrollViewluego 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)       
}

2
No es necesario combinar el diseño manual y automático para permitir una vista de pila desplazable. Simplemente necesita restringir el ancho y / o alto de la vista de la pila. Consulte mi respuesta para obtener detalles y un enlace a los documentos relevantes de Apple.
titaniumdecoy

El ejemplo de github funcionó perfectamente y fue muy claro. Gracias por compartir.
Behr

stackView.translatesAutoresizingMaskIntoConstraints = false, esto hizo el truco en mi caso
Howl Jenkins el

10

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 UIStackViewen tu UIScrollViewy establece las restricciones de anclaje de la UIStackViewpara 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

No agregue la misma respuesta a varias preguntas. Responda la mejor y marque el resto como duplicados. Consulte ¿Es aceptable agregar una respuesta duplicada a varias preguntas?
FelixSFD

10
No son realmente duplicados en este caso. La otra pregunta pregunta específicamente cómo hacerlo programáticamente, mientras que esta no. De hecho, la respuesta principal para esta pregunta proporciona un método GUI para lograr el resultado. No obstante, una solución programática puede ser valiosa para algunos usuarios.
Dalmazio

9

Al día para 2020.

100% storyboard O 100% código.


Aquí está la explicación más simple posible:

  1. Tener una escena en pantalla completa en blanco

  2. Agregar una vista de desplazamiento. Control-arrastre desde la vista de desplazamiento a la vista base , agregue izquierda-derecha-arriba-abajo, todo cero.

  3. 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.

  4. 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.

  1. Ahora configure el ancho de cada UILabel:

    Sorprendentemente, arrastre el control de la UILabelvista de desplazamiento a la vista de desplazamiento, no a la vista de pila, y seleccione anchos iguales .

Repetir:

No controle el arrastre del UILabel al padre del UILabel: vaya al abuelo. (En otras palabras, vaya hasta la vista de desplazamiento, no vaya a la vista de pila).

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...

Arrastre desde la vista de pila a la vista de desplazamiento y agregue una restricción de "ancho igual". Esto parece extraño porque ya lo anclaste de izquierda a derecha , pero así es como lo haces . No importa cuán extraño parezca, ese es el secreto.

Así que tienes dos opciones:

  1. Sorprendentemente, establezca el ancho de cada elemento en la vista de pila al ancho del abuelo scrollview ( no el padre de vista de pila ).

o

  1. Sorprendentemente, establezca un "ancho igual" de la vista de pila a la vista de desplazamiento, aunque de todos modos tiene los bordes izquierdo y derecho de la vista de pila anclados a la vista de desplazamiento.

Para ser claros, haga UNO de esos métodos, NO haga los dos.


8

Desplazamiento horizontal (UIStackView dentro de UIScrollView)

Para desplazamiento horizontal. Primero, cree ay UIStackViewa UIScrollViewy 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 translatesAutoresizingMaskIntoConstraintsa falseen el UIStackViewy el UIScrollView:

stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false

Para que todo funcione, los anclajes posteriores, superiores e inferiores UIStackViewdeben ser iguales a los UIScrollViewanclajes:

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 UIStackViewdebe ser igual o mayor que el ancho del UIScrollViewanclaje:

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 UIStackViewalineació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.

Inserciones de texto

Una característica adicional que puede resultarle útil es que, dado que se UIStackViewencuentra 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

7

Solo agregue esto a viewdidload:

let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
scrollVIew.contentInset = insets
scrollVIew.scrollIndicatorInsets = insets

fuente: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/LayoutUsingStackViews.html


No puedo decirte cuánto ahorró esto mis problemas de Autolayout + ScrollView. Gran descubrimiento en ese enlace de Apple.
ded

¿Que es lo que hace? ¿Qué pasa sin esto?
Miel

Esto solo coloca el contenido de la vista de desplazamiento debajo de la barra de estado.
probado el

2

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]; 
// ...

Hola Peter, en realidad es una gran idea! Las contribuciones son siempre bienvenidas, no dude en enviar una solicitud de extracción para esto. Mejor.
mgyky

0

Si tiene una restricción para centrar la Vista de pila verticalmente dentro de la vista de desplazamiento, simplemente quítela.


0

Ejemplo para una vista vertical de pila / vista de desplazamiento (usando EasyPeasypara 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!


0
  1. En primer lugar, diseñe su vista, preferiblemente en algo como Sketch u obtenga una idea de lo que desea como contenido desplazable.

  2. 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).

  3. 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.

  • Si aún no se logra, juegue con resistencia a la compresión o prioridad de contenido.
  • Después de esto, agregue restricciones a la vista de pila.
  • También piense en usar una UIView vacía como vista de relleno, si todo lo anterior no está dando resultados satisfactorios.

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 :).


0

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.

ingrese la descripción de la imagen aquí


0

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()

 }

0

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

ingrese la descripción de la imagen aquí

código: Stack View.Width = Scroll View.Widthes la clave.


0

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 UIStackViewtransició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.
  • Durante la transición, UIScrollViewintentará 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 UIScrollViewsu y adjustedContentInset, pero desde mi inicio de sesión en su layoutSubviewsmétodo, veo el siguiente comportamiento:

  • Uno agranda la ventana.
  • UIScrollView intenta reducir sus límites (ya que no es necesario el área debajo de la barra de herramientas).
  • UIStackView sigue.
  • De alguna manera UIScrollViewno 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.
  • Luego pase al paso 2.

Me parece que dos pasos solucionarían el problema:

  • Alinear UIStackView.topa UIScrollView.topMargin.
  • Establecer contentInsetAdjustmentBehaviora .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ó.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.