Adicionar visualizações no UIStackView programaticamente

158

Estou tentando adicionar modos de exibição no UIStackView programaticamente. Por enquanto, meu código é:

UIView *view1 = [[UIView alloc]init];
view1.backgroundColor = [UIColor blackColor];
[view1 setFrame:CGRectMake(0, 0, 100, 100)];

UIView *view2 =  [[UIView alloc]init];
view2.backgroundColor = [UIColor greenColor];
[view2 setFrame:CGRectMake(0, 100, 100, 100)];

[self.stack1 addArrangedSubview:view1];
[self.stack1 addArrangedSubview:view2];

Quando implanto o aplicativo, há apenas 1 visualização e ela é na cor preta. (View1 também obtém os parâmetros para view2)

Altimir Antonov
fonte
Sua sanidade checou sua tomada? Você registrou as subvisões em tempo de execução?
Wain
10
Use addArrangedSubview:, notaddSubview:
pkamb

Respostas:

245

As visualizações de pilha usam tamanho de conteúdo intrínseco; portanto, use restrições de layout para definir as dimensões das visualizações.

Existe uma maneira fácil de adicionar restrições rapidamente (exemplo):

[view1.heightAnchor constraintEqualToConstant:100].active = true;

Código completo:

- (void) setup {

    //View 1
    UIView *view1 = [[UIView alloc] init];
    view1.backgroundColor = [UIColor blueColor];
    [view1.heightAnchor constraintEqualToConstant:100].active = true;
    [view1.widthAnchor constraintEqualToConstant:120].active = true;


    //View 2
    UIView *view2 = [[UIView alloc] init];
    view2.backgroundColor = [UIColor greenColor];
    [view2.heightAnchor constraintEqualToConstant:100].active = true;
    [view2.widthAnchor constraintEqualToConstant:70].active = true;

    //View 3
    UIView *view3 = [[UIView alloc] init];
    view3.backgroundColor = [UIColor magentaColor];
    [view3.heightAnchor constraintEqualToConstant:100].active = true;
    [view3.widthAnchor constraintEqualToConstant:180].active = true;

    //Stack View
    UIStackView *stackView = [[UIStackView alloc] init];

    stackView.axis = UILayoutConstraintAxisVertical;
    stackView.distribution = UIStackViewDistributionEqualSpacing;
    stackView.alignment = UIStackViewAlignmentCenter;
    stackView.spacing = 30;


    [stackView addArrangedSubview:view1];
    [stackView addArrangedSubview:view2];
    [stackView addArrangedSubview:view3];

    stackView.translatesAutoresizingMaskIntoConstraints = false;
    [self.view addSubview:stackView];


    //Layout for Stack View
    [stackView.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor].active = true;
    [stackView.centerYAnchor constraintEqualToAnchor:self.view.centerYAnchor].active = true;
}

Nota: Isso foi testado no iOS 9

Espaçamento igual ao UIStackView (centralizado)

user1046037
fonte
26
O objetivo do uso de visualizações de pilha é que eles ocultam os detalhes do gerenciamento de restrições do desenvolvedor. Como você apontou, isso depende das subvisões com um tamanho de conteúdo intrínseco, padrão UIViewsque não. Se o pôster original tivesse usado UILabelinstâncias em vez de UIView, seu código teria funcionado como ele esperava. Eu adicionei um exemplo que demonstra isso abaixo.
Greg Brown
quando faço isso "[view1.heightAnchor constraintEqualToConstant: 100] .active = true;" im recebendo erro de ios 8.It de trabalhar apenas no iOS 9.I saber que uistackview é para ios 9 +, mas como posso definir heightAncor restrição para iOS 8
nuridin selimko
Meu StackView é adicionado usando o storyboard. Em tempo de execução, estou adicionando várias UIViews (contêm outras subvisões) ao stackView. Depois de carregar os dados, todas as visualizações no stackView têm a mesma altura e não são redimensionadas de acordo com o conteúdo. @ user1046037
Mansuu ....
Você tem restrições definidas para essas visualizações individuais para que o tamanho delas varie com base no conteúdo?
User1046037
Esta é a melhor & melhor maneira de gerenciar pontos de vista com larguras ou alturas iguais
Kampai
156

Swift 5.0

//Image View
let imageView = UIImageView()
imageView.backgroundColor = UIColor.blue
imageView.heightAnchor.constraint(equalToConstant: 120.0).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 120.0).isActive = true
imageView.image = UIImage(named: "buttonFollowCheckGreen")

//Text Label
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.yellow
textLabel.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
textLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
textLabel.text  = "Hi World"
textLabel.textAlignment = .center

//Stack View
let stackView   = UIStackView()
stackView.axis  = NSLayoutConstraint.Axis.vertical
stackView.distribution  = UIStackView.Distribution.equalSpacing
stackView.alignment = UIStackView.Alignment.center
stackView.spacing   = 16.0

stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(textLabel)
stackView.translatesAutoresizingMaskIntoConstraints = false

self.view.addSubview(stackView)

//Constraints
stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true

Com base na resposta @ user1046037.

Oleg Popov
fonte
2
Começando iOS 8 , é possível activar as restrições em lote usando activate(_:), e é normalmente mais eficiente de fazê-lo desta forma developer.apple.com/documentation/uikit/nslayoutconstraint/...
Jonathan Cabrera
Versão do Swift 5: stackoverflow.com/a/58827722/2273338
Abdurrahman Mubeen Ali
17

UIStackViewusa restrições internamente para posicionar suas subvisões organizadas. Exatamente quais restrições são criadas depende de como a própria exibição da pilha está configurada. Por padrão, uma exibição de pilha criará restrições que exibem suas subvisões organizadas em uma linha horizontal, fixando as vistas iniciais e finais às suas próprias arestas iniciais e finais. Portanto, seu código produziria um layout parecido com este:

|[view1][view2]|

O espaço alocado para cada subvisão é determinado por vários fatores, incluindo o tamanho do conteúdo intrínseco da subvisão e sua resistência à compactação e prioridades de abrangência de conteúdo. Por padrão, as UIViewinstâncias não definem um tamanho de conteúdo intrínseco. Isso geralmente é fornecido por uma subclasse, como UILabelou UIButton.

Como a resistência à compactação de conteúdo e as prioridades de abrangência de conteúdo de duas novas UIViewinstâncias serão as mesmas, e nenhuma visualização fornece um tamanho intrínseco de conteúdo, o mecanismo de layout deve adivinhar qual tamanho deve ser alocado para cada visualização. No seu caso, ele está atribuindo a primeira visualização 100% do espaço disponível, e nada para a segunda visualização.

Se você modificar seu código para usar UILabelinstâncias, obterá melhores resultados:

UILabel *label1 = [UILabel new];
label1.text = @"Label 1";
label1.backgroundColor = [UIColor blueColor];

UILabel *label2 = [UILabel new];
label2.text = @"Label 2";
label2.backgroundColor = [UIColor greenColor];

[self.stack1 addArrangedSubview:label1];
[self.stack1 addArrangedSubview:label2];

Observe que não é necessário criar explicitamente nenhuma restrição. Esse é o principal benefício do uso UIStackView- oculta os detalhes (geralmente feios) do gerenciamento de restrições do desenvolvedor.

Greg Brown
fonte
Eu tenho o mesmo problema que isso funciona para rótulos, mas não para TextFields (ou modos de exibição). Além disso, todos os tutoriais encontrados usam etiquetas, imagens ou botões. Isso significa que, para algo diferente desses elementos da interface do usuário, não podemos usar esse método?
physicalattraction
@physicalattraction As visualizações adicionadas a uma visualização em pilha devem ter um tamanho de conteúdo intrínseco. Pelo que me lembro, o tamanho intrínseco de um campo de texto é baseado no conteúdo do campo (o que é ímpar). As instâncias por UIViewsi só não têm um tamanho intrínseco. Pode ser necessário aplicar restrições adicionais a essas visualizações, para que a visualização da pilha saiba o tamanho delas.
Greg Brown
Faz sentido, então eu estou tentando. Agora eu construí uma View em um xib com um campo de texto, ao qual dou uma restrição explícita de altura de 30 pixels. Além disso, faço as duas restrições a seguir: MyTextField.top = top+20e bottom = MyTextField.bottom+20. Eu esperaria que isso desse à minha opinião uma altura intrínseca de 70, mas, em vez disso, reclama de restrições conflitantes. Você sabe o que está acontecendo aqui? É essa visão que eu quero colocar dentro do meu UIStackView.
physicalattraction
Eu acho que sei qual é o problema. Uma exibição de pilha fixa automaticamente sua subvisão final arranjada à sua borda inferior. Portanto, a visualização de pilha está tentando fazer com que seu contêiner veja o mesmo tamanho que ele. No entanto, você disse a essa visão que ela deve ter 70 pixels de altura, o que cria um conflito. Tente criar uma exibição vazia adicional imediatamente após a exibição do contêiner e atribua a ele uma prioridade vertical de 0. Dê ao seu contêiner uma prioridade vertical de 1000. Isso fará com que a exibição vazia se estenda para preencher o espaço vazio na pilha Visão.
Greg Brown
Estou assumindo que você está usando uma exibição de pilha vertical a propósito. Você também pode conferir este artigo que escrevi sobre visualizações de pilha: dzone.com/articles/…
Greg Brown
13

No Swift 4.2

let redView = UIView()
    redView.backgroundColor = .red

    let blueView = UIView()
    blueView.backgroundColor = .blue

    let stackView = UIStackView(arrangedSubviews: [redView, blueView])
    stackView.axis = .vertical
    stackView.distribution = .fillEqually

    view.addSubview(stackView)

    //        stackView.frame = CGRect(x: 0, y: 0, width: 200, height: 200)

    // autolayout constraint
    stackView.translatesAutoresizingMaskIntoConstraints = false

NSLayoutConstraint.activate([stackView.topAnchor.constraint(equalTo: view.topAnchor), stackView.leftAnchor.constraint(equalTo: view.leftAnchor), stackView.rightAnchor.constraint(equalTo: view.rightAnchor), stackView.heightAnchor.constraint(equalToConstant: 200)])
Ashim Dahal
fonte
8

Você precisa definir seu tipo de distribuição. No seu código, juste adicione:

self.stack1.distribution = UIStackViewDistributionFillEqually;

Ou você pode definir a distribuição diretamente no seu construtor de interface. Por exemplo:

insira a descrição da imagem aqui

Espero que ajude;) Lapinou.

Lapinou
fonte
8

Após duas linhas, meu problema foi corrigido

    view.heightAnchor.constraintEqualToConstant(50).active = true;
    view.widthAnchor.constraintEqualToConstant(350).active = true;

Versão Swift -

    var DynamicView=UIView(frame: CGRectMake(100, 200, 100, 100))
    DynamicView.backgroundColor=UIColor.greenColor()
    DynamicView.layer.cornerRadius=25
    DynamicView.layer.borderWidth=2
    self.view.addSubview(DynamicView)
    DynamicView.heightAnchor.constraintEqualToConstant(50).active = true;
    DynamicView.widthAnchor.constraintEqualToConstant(350).active = true;

    var DynamicView2=UIView(frame: CGRectMake(100, 310, 100, 100))
    DynamicView2.backgroundColor=UIColor.greenColor()
    DynamicView2.layer.cornerRadius=25
    DynamicView2.layer.borderWidth=2
    self.view.addSubview(DynamicView2)
    DynamicView2.heightAnchor.constraintEqualToConstant(50).active = true;
    DynamicView2.widthAnchor.constraintEqualToConstant(350).active = true;

    var DynamicView3:UIView=UIView(frame: CGRectMake(10, 420, 355, 100))
    DynamicView3.backgroundColor=UIColor.greenColor()
    DynamicView3.layer.cornerRadius=25
    DynamicView3.layer.borderWidth=2
    self.view.addSubview(DynamicView3)

    let yourLabel:UILabel = UILabel(frame: CGRectMake(110, 10, 200, 20))
    yourLabel.textColor = UIColor.whiteColor()
    //yourLabel.backgroundColor = UIColor.blackColor()
    yourLabel.text = "mylabel text"
    DynamicView3.addSubview(yourLabel)
    DynamicView3.heightAnchor.constraintEqualToConstant(50).active = true;
    DynamicView3.widthAnchor.constraintEqualToConstant(350).active = true;

    let stackView   = UIStackView()
    stackView.axis  = UILayoutConstraintAxis.Vertical
    stackView.distribution  = UIStackViewDistribution.EqualSpacing
    stackView.alignment = UIStackViewAlignment.Center
    stackView.spacing   = 30

    stackView.addArrangedSubview(DynamicView)
    stackView.addArrangedSubview(DynamicView2)
    stackView.addArrangedSubview(DynamicView3)

    stackView.translatesAutoresizingMaskIntoConstraints = false;

    self.view.addSubview(stackView)

    //Constraints
    stackView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
    stackView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor).active = true
Ekta
fonte
8

Para a resposta aceita ao tentar ocultar qualquer exibição dentro da exibição de pilha, a restrição não está correta.

Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. 
    Try this: 
        (1) look at each constraint and try to figure out which you don't expect; 
        (2) find the code that added the unwanted constraint or constraints and fix it. 
(
    "<NSLayoutConstraint:0x618000086e50 UIView:0x7fc11c4051c0.height == 120   (active)>",
    "<NSLayoutConstraint:0x610000084fb0 'UISV-hiding' UIView:0x7fc11c4051c0.height == 0   (active)>"
)

A razão é que, quando ocultar o view, stackViewele definirá a altura como 0 para animá-lo.

Solução altere a restrição prioritycomo abaixo.

import UIKit

class ViewController: UIViewController {

    let stackView = UIStackView()
    let a = UIView()
    let b = UIView()

    override func viewDidLoad() {
        super.viewDidLoad()

        a.backgroundColor = UIColor.red
        a.widthAnchor.constraint(equalToConstant: 200).isActive = true
        let aHeight = a.heightAnchor.constraint(equalToConstant: 120)
        aHeight.isActive = true
        aHeight.priority = 999

        let bHeight = b.heightAnchor.constraint(equalToConstant: 120)
        bHeight.isActive = true
        bHeight.priority = 999
        b.backgroundColor = UIColor.green
        b.widthAnchor.constraint(equalToConstant: 200).isActive = true

        view.addSubview(stackView)
        stackView.backgroundColor = UIColor.blue
        stackView.addArrangedSubview(a)
        stackView.addArrangedSubview(b)
        stackView.axis = .vertical
        stackView.distribution = .equalSpacing
        stackView.translatesAutoresizingMaskIntoConstraints = false
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // Just add a button in xib file or storyboard and add connect this action.
    @IBAction func test(_ sender: Any) {
        a.isHidden = !a.isHidden
    }

}
William Hu
fonte
5
    //Image View
    let imageView               = UIImageView()
    imageView.backgroundColor   = UIColor.blueColor()
    imageView.heightAnchor.constraintEqualToConstant(120.0).active = true
    imageView.widthAnchor.constraintEqualToConstant(120.0).active = true
    imageView.image = UIImage(named: "buttonFollowCheckGreen")

    //Text Label
    let textLabel               = UILabel()
    textLabel.backgroundColor   = UIColor.greenColor()
    textLabel.widthAnchor.constraintEqualToConstant(self.view.frame.width).active = true
    textLabel.heightAnchor.constraintEqualToConstant(20.0).active = true
    textLabel.text  = "Hi World"
    textLabel.textAlignment = .Center


    //Third View
    let thirdView               = UIImageView()
    thirdView.backgroundColor   = UIColor.magentaColor()
    thirdView.heightAnchor.constraintEqualToConstant(120.0).active = true
    thirdView.widthAnchor.constraintEqualToConstant(120.0).active = true
    thirdView.image = UIImage(named: "buttonFollowMagenta")


    //Stack View
    let stackView   = UIStackView()
    stackView.axis  = UILayoutConstraintAxis.Vertical
    stackView.distribution  = UIStackViewDistribution.EqualSpacing
    stackView.alignment = UIStackViewAlignment.Center
    stackView.spacing   = 16.0

    stackView.addArrangedSubview(imageView)
    stackView.addArrangedSubview(textLabel)
    stackView.addArrangedSubview(thirdView)
    stackView.translatesAutoresizingMaskIntoConstraints = false;

    self.view.addSubview(stackView)

    //Constraints
    stackView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor).active = true
    stackView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor).active = true

Melhorado na resposta por @Oleg Popov

Arunabh Das
fonte
4

No meu caso, o que eu estava mexendo seria que eu estava perdendo essa linha:

stackView.translatesAutoresizingMaskIntoConstraints = false;

Depois disso, não há necessidade de definir restrições para minhas subvisões organizadas, o stackview está cuidando disso.

PakitoV
fonte
4

Versão Swift 5 da resposta de Oleg Popov , que é baseada na resposta de user1046037

//Image View
let imageView = UIImageView()
imageView.backgroundColor = UIColor.blue
imageView.heightAnchor.constraint(equalToConstant: 120.0).isActive = true
imageView.widthAnchor.constraint(equalToConstant: 120.0).isActive = true
imageView.image = UIImage(named: "buttonFollowCheckGreen")

//Text Label
let textLabel = UILabel()
textLabel.backgroundColor = UIColor.yellow
textLabel.widthAnchor.constraint(equalToConstant: self.view.frame.width).isActive = true
textLabel.heightAnchor.constraint(equalToConstant: 20.0).isActive = true
textLabel.text  = "Hi World"
textLabel.textAlignment = .center

//Stack View
let stackView   = UIStackView()
stackView.axis  = NSLayoutConstraint.Axis.vertical
stackView.distribution  = UIStackView.Distribution.equalSpacing
stackView.alignment = UIStackView.Alignment.center
stackView.spacing   = 16.0

stackView.addArrangedSubview(imageView)
stackView.addArrangedSubview(textLabel)
stackView.translatesAutoresizingMaskIntoConstraints = false

self.view.addSubview(stackView)

//Constraints
stackView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor).isActive = true
Abdurrahman Mubeen Ali
fonte
1

Acabei de me deparar com um problema muito semelhante. Assim como mencionado antes, as dimensões da visualização da pilha dependem de um tamanho de conteúdo intrínseco das subvisões organizadas. Aqui está minha solução no Swift 2.xe seguinte estrutura de exibição:

view - UIView

customView - CustomView: UIView

stackView - UISTackView

subvisões organizadas - subclasses personalizadas do UIView

    //: [Previous](@previous)

import Foundation
import UIKit
import XCPlayground

/**Container for stack view*/
class CustomView:UIView {

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }


    init(){
        super.init(frame: CGRectZero)

    }

}

/**Custom Subclass*/
class CustomDrawing:UIView{
    override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()

    }

    func setup(){
       // self.backgroundColor = UIColor.clearColor()
        print("setup \(frame)")
    }

    override func drawRect(rect: CGRect) {
        let ctx = UIGraphicsGetCurrentContext()
        CGContextMoveToPoint(ctx, 0, 0)
        CGContextAddLineToPoint(ctx, CGRectGetWidth(bounds), CGRectGetHeight(bounds))
        CGContextStrokePath(ctx)

        print("DrawRect")

    }
}



//: [Next](@next)
let stackView = UIStackView()
stackView.distribution = .FillProportionally
stackView.alignment = .Center
stackView.axis = .Horizontal
stackView.spacing = 10


//container view
let view = UIView(frame: CGRectMake(0,0,320,640))
view.backgroundColor = UIColor.darkGrayColor()
//now custom view

let customView = CustomView()

view.addSubview(customView)

customView.translatesAutoresizingMaskIntoConstraints = false
customView.widthAnchor.constraintEqualToConstant(220).active = true
customView.heightAnchor.constraintEqualToConstant(60).active = true
customView.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor).active = true
customView.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor).active = true
customView.backgroundColor = UIColor.lightGrayColor()

//add a stack view
customView.addSubview(stackView)
stackView.centerXAnchor.constraintEqualToAnchor(customView.centerXAnchor).active = true
stackView.centerYAnchor.constraintEqualToAnchor(customView.centerYAnchor).active = true
stackView.translatesAutoresizingMaskIntoConstraints = false


let c1 = CustomDrawing()
c1.translatesAutoresizingMaskIntoConstraints = false
c1.backgroundColor = UIColor.redColor()
c1.widthAnchor.constraintEqualToConstant(30).active = true
c1.heightAnchor.constraintEqualToConstant(30).active = true

let c2 = CustomDrawing()
c2.translatesAutoresizingMaskIntoConstraints = false
c2.backgroundColor = UIColor.blueColor()
c2.widthAnchor.constraintEqualToConstant(30).active = true
c2.heightAnchor.constraintEqualToConstant(30).active = true


stackView.addArrangedSubview(c1)
stackView.addArrangedSubview(c2)


XCPlaygroundPage.currentPage.liveView = view
Janusz Chudzynski
fonte
-2

Experimente o código abaixo,

UIView *view1 = [[UIView alloc]init];
view1.backgroundColor = [UIColor blackColor];
[view1 setFrame:CGRectMake(0, 0, 50, 50)];

UIView *view2 =  [[UIView alloc]init];
view2.backgroundColor = [UIColor greenColor];
[view2 setFrame:CGRectMake(0, 100, 100, 100)];

NSArray *subView = [NSArray arrayWithObjects:view1,view2, nil];

[self.stack1 initWithArrangedSubviews:subView];

Espero que funcione. Entre em contato se precisar de mais esclarecimentos.

Nilesh Patel
fonte