É possível rolar o UIStackView?


210

Digamos que adicionei mais visualizações no UIStackView que podem ser exibidas, como posso fazer a UIStackViewrolagem?


Por que você não está usando uma exibição de rolagem com uma (ou várias) visualizações de pilha para o conteúdo?
Wain

2
A solução de uma frase é que, para a largura, você controla e arrasta para a exibição de rolagem dos avós, NÃO para a exibição da pilha pai. Explicado na minha resposta!
Fattie

Como incorporar o UIStackView no UIScrollView no iOS github.com/onmyway133/blog/issues/324
onmyway133

Em relação a esta questão crítica no iOS. De fato, existem duas (e apenas duas) maneiras de fazer isso - essas são a única maneira de fazer isso e você deve seguir exatamente um dos dois procedimentos. Minha resposta abaixo é sempre atualizada.
Fattie

SIM. Você DEVE ter um UIView intermediário como exibição de conteúdo do UIScrollView. Em seguida, coloque o UIStackView como exibição filho da exibição de conteúdo. raywenderlich.com/...
poGUIst

Respostas:


578

Caso alguém esteja procurando uma solução sem código , criei um exemplo para fazer isso completamente no storyboard, usando o Layout automático.

Você pode obtê-lo no github .

Basicamente, para recriar o exemplo (para rolagem vertical):

  1. Crie ae UIScrollViewdefina suas restrições.
  2. Adicione um UIStackViewaoUIScrollView
  3. Defina as restrições: Leading, Trailing, Tope Bottomdeve ser igual para os deUIScrollView
  4. Configure uma Widthrestrição igual entre UIStackViewe UIScrollView.
  5. Defina Eixo = Vertical, Alinhamento = Preenchimento, Distribuição = Espaçamento Igual e Espaçamento = 0 na UIStackView
  6. Adicione um número de UIViewsaoUIStackView
  7. Corre

Troque Widthpela Heightetapa 4 e defina Axis= Horizontalna etapa 5, para obter um UIStackView horizontal.


55
Obrigado! "4. Configure uma restrição de largura igual entre o UIStackView e o UIScrollView." foi a chave;)
branco

19
O número 6. também é importante. Queria adicionar todas as visualizações no código, mas o Layout automático mostra restrições ausentes no IB.
pré

11
Oi gaes, parece que esta solução não funciona para rolagem horizontal. Na etapa 4, em vez de definir a mesma largura, presumo definir a mesma altura e Axis = Horizontal. Eu tentei isso e não funciona.
Seto Elkahfi

5
A adição de uma restrição para que o stackview e o scrollview tenham larguras iguais removerão os avisos do construtor de interface, mas a rolagem será desativada. A rolagem ocorre apenas quando a exibição do conteúdo dentro da visualização de rolagem é maior que a visualização de rolagem!
Jason Moore

6
Para elaborar o ponto 6. Se a visualização da pilha não tiver elementos, mas todas as restrições estiverem no lugar, você obtém "Visualização de rolagem Necessita de restrições para a posição ou altura Y". A adição de um rótulo na exibição da pilha faz com que esse aviso desapareça!
Faisal Memon

45

O Guia de layout automático da Apple inclui uma seção inteira sobre Trabalho com vistas de rolagem . Alguns trechos relevantes:

  1. Fixar as bordas superior, inferior, dianteira e posterior da visualização de conteúdo nas bordas correspondentes da visualização de rolagem. A visualização de conteúdo agora define a área de conteúdo da visualização de rolagem.
  2. (Opcional) Para desativar a rolagem horizontal, defina a largura da visualização de conteúdo igual à largura da visualização de rolagem. A visualização de conteúdo agora preenche a visualização de rolagem horizontalmente.
  3. (Opcional) Para desativar a rolagem vertical, defina a altura da exibição de conteúdo igual à altura da exibição de rolagem. A visualização de conteúdo agora preenche a visualização de rolagem horizontalmente.

Além disso:

Seu layout deve definir completamente o tamanho da exibição do conteúdo (exceto onde definido nas etapas 5 e 6). … Quando a exibição de conteúdo é mais alta que a exibição de rolagem, a exibição de rolagem ativa a rolagem vertical. Quando a exibição do conteúdo é maior que a exibição de rolagem, a exibição de rolagem ativa a rolagem horizontal.

Para resumir, a exibição de conteúdo da exibição de rolagem (nesse caso, uma exibição de pilha) deve ser fixada nas bordas e ter sua largura e / ou altura restritas. Isso significa que o conteúdo da visualização de pilha deve ser restrito (direta ou indiretamente) nas direções em que a rolagem é desejada, o que pode significar adicionar uma restrição de altura a cada visualização dentro de uma visualização de pilha de rolagem vertical, por exemplo. A seguir, é apresentado um exemplo de como permitir a rolagem vertical de uma exibição de rolagem que contém uma exibição de pilha:

// 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
Obrigado, mas a omissão da restrição de altura (para permitir a rolagem vertical) gera esse erro no Storyboard: Need constraints for: Y position or height.Esse erro desaparece se você definir uma restrição de largura e altura para a exibição da pilha, que desativa a rolagem vertical. Esse código ainda funciona para você?
Crashalot

@Crashalot Para eliminar esse erro, você precisa adicionar uma subview à exibição da pilha; veja minha resposta abaixo.
Pkamb # 14/18

16

As restrições na resposta mais votada aqui funcionaram para mim e colei uma imagem das restrições abaixo, criada no meu storyboard.

Eu atingi dois problemas, porém, de que outros deveriam estar cientes:

  1. Depois de adicionar restrições semelhantes às da resposta aceita, recebia o erro vermelho de execução automática Need constraints for: X position or width. Isso foi resolvido adicionando um UILabel como uma subvisualização da visualização da pilha.

    Estou adicionando as subvisões programaticamente, então, originalmente, não tinha subvisões no storyboard. Para se livrar dos erros de execução automática, adicione uma subvisão ao storyboard e remova-a quando estiver carregando antes de adicionar suas subvisões e restrições reais.

  2. Inicialmente, tentei adicionar UIButtonsao UIStackView. Os botões e as visualizações seriam carregados, mas a visualização de rolagem não rolaria . Isso foi resolvido adicionandoUILabels se à exibição de pilha em vez de botões. Usando as mesmas restrições, essa hierarquia de exibição com os UILabels rola, mas os UIButtons não.

    Eu estou confuso com este problema, como os UIButtons não parecem ter uma IntrinsicContentSize (usado pelo Ver Stack). Se alguém souber por que os botões não funcionam, eu adoraria saber o porquê.

Aqui está minha hierarquia e restrições de exibição, para referência:

restrições para exibição em pilha na exibição por rolagem [1]


3
você me salvou várias horas de cabeça batendo contra a parede e frustração; estou exatamente na mesma situação e não consegui descobrir o motivo do aviso e por que não está trabalhando com botões. Por que a Apple, por que !?
PakitoV 28/02

15

Como Eik diz, UIStackViewe UIScrollViewjogue bem juntos, veja aqui .

A chave é que ele UIStackViewlida com a variável altura / largura para diferentes conteúdos e, em UIScrollViewseguida, faz seu trabalho bem ao rolar / saltar esse conteúdo:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)       
}

2
Não é necessário combinar o layout manual e automático para permitir uma exibição de pilha rolável. Você simplesmente precisa restringir a largura e / ou a altura da visualização da pilha. Veja minha resposta para obter detalhes e um link para os documentos relevantes da Apple.
titaniumdecoy

O exemplo do github funcionou perfeitamente e foi muito claro. Obrigado por compartilhar.
Behr

stackView.translatesAutoresizingMaskIntoConstraints = false, este fez o truque no meu caso
Howl Jenkins

10

Eu estava olhando para fazer a mesma coisa e me deparei com este excelente post. Se você deseja fazer isso programaticamente usando a API âncora, este é o caminho a percorrer.

Para resumir, incorpore o seu UIStackViewno seu UIScrollViewe defina as restrições de âncora do UIStackViewpara coincidir com as do 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

Não adicione a mesma resposta a várias perguntas. Responda à melhor e marque o restante como duplicado. Consulte É aceitável adicionar uma resposta duplicada a várias perguntas?
perfil completo de FelixSFD

10
Eles não são realmente duplicados neste caso. A outra pergunta pergunta especificamente como fazê-lo programaticamente, enquanto este não o faz. De fato, a resposta principal para essa pergunta fornece um método GUI para alcançar o resultado. No entanto, uma solução programática pode ser valiosa para alguns usuários.
Dalmazio

9

Atualizado para 2020.

Storyboard 100% OU código 100%.


Aqui está a explicação mais simples possível:

  1. Tenha uma cena em tela cheia em branco

  2. Adicione uma visualização de rolagem. Arraste com a tecla Control pressionada da visualização de rolagem para a visualização de base , adicione esquerda, direita, superior e inferior, todos os zero.

  3. Adicione uma exibição de pilha na exibição de rolagem. Arraste com a tecla Control pressionada da exibição da pilha para a exibição de rolagem , adicione esquerda, direita, superior e inferior, todos os zero.

  4. Coloque duas ou três etiquetas dentro da visualização da pilha.

Para maior clareza, torne a cor de fundo do rótulo vermelha. Defina a altura da etiqueta como 100.

  1. Agora defina a largura de cada UILabel:

    Surpreendentemente, arraste com o controle UILabelda exibição de rolagem, não da pilha e selecione larguras iguais .

Repetir:

Não controle o arrasto do UILabel para o pai do UILabel - vá para o avô. (Em outras palavras, vá até a exibição de rolagem, não vá para a exibição de pilha.)

É simples assim. Esse é o segredo.

Dica secreta - bug da Apple:

Não funcionará com apenas um item! Adicione alguns rótulos para fazer a demonstração funcionar.

Você Terminou.

Dica: você deve adicionar uma altura a cada novo item . Cada item em qualquer exibição de pilha de rolagem deve ter um tamanho intrínseco (como um rótulo) ou adicionar uma restrição de altura explícita.


A abordagem alternativa:

Acima: surpreendentemente, defina as larguras dos UILabels com a largura da exibição de rolagem (não da exibição de pilha).

Como alternativa ...

Arraste da exibição de pilha para a exibição de rolagem e adicione uma restrição de "largura igual". Isso parece estranho porque você já fixou a esquerda-direita , mas é assim que você faz . Por mais estranho que pareça, esse é o segredo.

Então você tem duas opções:

  1. Surpreendentemente, defina a largura de cada item na exibição da pilha como a largura do avô de exibição de rolagem ( não o pai da exibição de pilha ).

ou

  1. Surpreendentemente, defina uma "largura igual" da stackview como a scrollview - mesmo que você tenha as bordas esquerda e direita da stackview afixadas à scrollview de qualquer maneira.

Para ser claro, execute um desses métodos, NÃO faça os dois.


8

Rolagem horizontal (UIStackView no UIScrollView)

Para rolagem horizontal. Primeiro, crie ae UIStackViewa UIScrollViewe adicione-os à sua exibição da seguinte maneira:

let scrollView = UIScrollView()
let stackView = UIStackView()

scrollView.addSubview(stackView)
view.addSubview(scrollView)

Lembre-se de definir o translatesAutoresizingMaskIntoConstraintspara falseno UIStackViewe o UIScrollView:

stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false

Para que tudo funcione, as âncoras à direita, à esquerda e à direita UIStackViewdevem ser iguais às UIScrollViewâncoras:

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

Mas a âncora de largura do UIStackViewmosto deve ser igual ou maior que a largura da UIScrollViewâncora:

stackView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor).isActive = true

Agora ancore seu UIScrollView, por exemplo:

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 

Em seguida, sugiro tentar as seguintes configurações para o UIStackViewalinhamento e distribuição:

topicStackView.axis = .horizontal
topicStackView.distribution = .equalCentering
topicStackView.alignment = .center

topicStackView.spacing = 10

Finalmente, você precisará usar o addArrangedSubview:método para adicionar subviews ao seu UIStackView.

Inserções de texto

Um recurso adicional que você pode achar útil é que, como o conteúdo UIStackViewé mantido em um, UIScrollViewvocê agora tem acesso a inserções de texto para tornar as coisas um pouco mais 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

Basta adicionar isso a viewdidload:

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

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


Não posso dizer quanto isso salvou meus problemas com o Autolayout + ScrollView. Ótima descoberta nesse link da Apple.
ded

o que faz? O que acontece sem isso?
Mel

Isso coloca apenas o conteúdo da exibição de rolagem abaixo da barra de status.
turingtested

2

Você pode tentar o ScrollableStackView : https://github.com/gurhub/ScrollableStackView

É uma biblioteca compatível com Objective-C e Swift. Está disponível através do CocoaPods .

Código de exemplo (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 exemplo (Objective-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]; 
// ...

Olá Peter, realmente uma ótima idéia! As contribuições são sempre bem-vindas, não hesite em enviar uma solicitação de recebimento para isso. Melhor.
mgyky

0

Se você tiver uma restrição para centralizar a Vista de pilha verticalmente dentro da vista de rolagem, basta removê-la.


0

Exemplo para um stackview / scrollview vertical (usando o EasyPeasyfor 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)
]

Apenas certifique-se de que a altura de cada subvisão esteja definida!


0
  1. Antes de tudo, projete sua visualização, de preferência em algo como Sketch ou tenha uma idéia do que você deseja como conteúdo rolável.

  2. Depois disso, crie o controlador de exibição de forma livre (escolha no inspetor de atributos) e defina altura e largura de acordo com o tamanho do conteúdo intrínseco da sua exibição (a ser escolhido no inspetor de tamanho).

  3. Depois disso, no controlador de exibição, coloque uma exibição de rolagem e isso é uma lógica, que eu descobri que funciona quase o tempo todo no iOS (pode ser necessário passar pela documentação dessa classe de exibição que pode ser obtida através do comando + clique em nessa aula ou no Google)

    Se você estiver trabalhando com duas ou mais visualizações, inicie primeiro com uma visualização que foi introduzida anteriormente ou seja mais primitiva e, em seguida, vá para a visualização que foi introduzida posteriormente ou que é mais moderna. Então, aqui, desde que a exibição de rolagem foi introduzida primeiro, comece com a exibição de rolagem primeiro e depois vá para a exibição de pilha. Aqui, coloque as restrições da visualização de rolagem em zero em todas as direções em relação à sua super visualização. Coloque todas as suas visualizações nessa exibição de rolagem e, em seguida, coloque-as na exibição de pilha.

Ao trabalhar com a exibição de pilha

  • Primeiro, inicie com motivos de cima para baixo (abordagem de baixo para cima), ou seja, se você tiver rótulos, campos de texto e imagens em sua exibição, expanda essas exibições primeiro (dentro da exibição de rolagem) e depois coloque-as na exibição de pilha.

  • Depois disso, ajuste a propriedade da exibição de pilha. Se a visualização desejada ainda não for alcançada, use outra visualização da pilha.

  • Se ainda não for alcançado, brinque com resistência à compressão ou com conteúdo prioritário.
  • Depois disso, adicione restrições à exibição da pilha.
  • Pense também em usar um UIView vazio como exibição de preenchimento, se todas as opções acima não estiverem apresentando resultados satisfatórios.

Depois de fazer sua exibição, coloque uma restrição entre a exibição da pilha mãe e a exibição de rolagem, enquanto os filhos da restrição empilham a exibição com a exibição da pilha mãe. Esperançosamente, a essa altura, ele deve funcionar bem ou você pode receber um aviso do Xcode dando sugestões, leia o que diz e implemente-as. Espero que agora você tenha uma visão de trabalho de acordo com suas expectativas :).


0

Para a visualização de pilha aninhada ou única, a visualização de rolagem deve ser definida com uma largura fixa com a visualização raiz. A exibição da pilha principal que está dentro da exibição de rolagem deve definir a mesma largura. [Minha visualização de rolagem está abaixo de uma Visualização, ignore-a]

Configure uma restrição de largura igual entre o UIStackView e o UIScrollView.

insira a descrição da imagem aqui


0

Se alguém procurar horizontalmente scrollview

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 uma exibição de rolagem em sua cena e dimensione-a para que ela preencha a cena. Em seguida, coloque uma vista de pilha dentro da vista de rolagem e coloque o botão adicionar item dentro da vista de pilha. Assim que tudo estiver no lugar, defina as seguintes restrições:

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

insira a descrição da imagem aqui

código: Stack View.Width = Scroll View.Widthé a chave.


0

Adicionando uma nova perspectiva para o macOS Catalyst. Como os aplicativos macOS suportam o redimensionamento de janelas, é possível que você UIStackViewfaça a transição de um status não rolável para um status rolável ou vice-versa. Há duas coisas sutis aqui:

  • UIStackView foi projetado para caber em todas as áreas possíveis.
  • Durante a transição, UIScrollViewtentará redimensionar seus limites para dar conta da área recém-adquirida / perdida sob a barra de navegação (ou barra de ferramentas no caso de aplicativos macOS).

Infelizmente, isso criará um loop infinito. Eu não estou extremamente familiarizado com isso UIScrollViewe adjustedContentInset, mas do meu log no layoutSubviewsmétodo, estou vendo o seguinte comportamento:

  • Um amplia a janela.
  • UIScrollView tenta reduzir seus limites (já que não há necessidade da área abaixo da barra de ferramentas).
  • UIStackView segue.
  • De alguma forma, UIScrollViewestá insatisfeito e decide restaurar os limites maiores. Isso me parece muito estranho, pois o que estou vendo no log é isso UIScrollView.bounds.height == UIStackView.bounds.height.
  • UIStackView segue.
  • Em seguida, passe para a etapa 2.

Parece-me que duas etapas resolveriam o problema:

  • Alinhar UIStackView.topa UIScrollView.topMargin.
  • Defina contentInsetAdjustmentBehaviorcomo .never.

Aqui estou preocupado com uma exibição verticalmente rolável com um crescimento vertical UIStackView. Para um par horizontal, altere o código adequadamente.

Espero que ajude alguém no futuro. Não foi possível encontrar alguém mencionando isso na Internet e me custou muito tempo para descobrir o que aconteceu.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.