Agregar vistas en UIStackView mediante programación

158

Estoy tratando de agregar vistas en UIStackView mediante programación. Por ahora mi código es:

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

Cuando implemento la aplicación, solo hay 1 vista y es de color negro (view1 también obtiene los parámetros para view2)

Altimir Antonov
fuente
Usted cordura comprobó su salida? ¿Registró las subvistas en tiempo de ejecución?
Wain
10
Uso addArrangedSubview:, noaddSubview:
pkamb

Respuestas:

245

Las vistas de pila utilizan el tamaño de contenido intrínseco, por lo tanto, use restricciones de diseño para definir las dimensiones de las vistas.

Hay una manera fácil de agregar restricciones rápidamente (ejemplo):

[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: Esto fue probado en iOS 9

UIStackView Equal Spacing (centrado)

usuario1046037
fuente
26
El punto de usar vistas de pila es que ocultan los detalles de la gestión de restricciones del desarrollador. Como señaló, esto se basa en que las subvistas tienen un tamaño de contenido intrínseco, que por defecto UIViewsno. Si el póster original hubiera usado UILabelinstancias en lugar de UIView, su código habría funcionado como esperaba. Agregué un ejemplo que demuestra esto a continuación.
Greg Brown
cuando hago esto "[view1.heightAnchor restrictintEqualToConstant: 100] .active = true;" Im consiguiendo error en ios 8.It de trabajo sólo en ios conocimientos 9.I que uistackview es para iOS 9+ pero ¿cómo puedo establecer limitación heightAncor para iOS 8
Nuridin selimko
Mi StackView se agrega usando storyboard. En tiempo de ejecución estoy agregando varias UIViews (contienen otras subvistas) a stackView. Después de cargar datos, todas las vistas dentro de stackView tienen la misma altura, no se redimensionan según el contenido. @ user1046037
Mansuu ....
¿Tiene restricciones establecidas para esas vistas individuales para que su tamaño varíe según el contenido?
user1046037
Esta es la mejor y óptima manera de administrar vistas con anchos o alturas iguales
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

Basado en la respuesta @ user1046037.

Oleg Popov
fuente
2
A partir de iOS 8 , es posible activar las restricciones en el uso por lotes activate(_:), y generalmente es más eficiente hacerlo de esta manera developer.apple.com/documentation/uikit/nslayoutconstraint/…
Jonathan Cabrera
Versión Swift 5: stackoverflow.com/a/58827722/2273338
Abdurrahman Mubeen Ali el
17

UIStackViewusa restricciones internamente para colocar sus subvistas organizadas. Exactamente qué restricciones se crean depende de cómo esté configurada la vista de la pila. De forma predeterminada, una vista de pila creará restricciones que expondrán sus subvistas organizadas en una línea horizontal, fijando las vistas iniciales y finales a sus propios bordes iniciales y finales. Entonces su código produciría un diseño que se ve así:

|[view1][view2]|

El espacio que se asigna a cada subvista está determinado por una serie de factores, incluido el tamaño del contenido intrínseco de la subvista y su resistencia a la compresión y las prioridades de contenido. Por defecto, las UIViewinstancias no definen un tamaño de contenido intrínseco. Esto es algo que generalmente proporciona una subclase, como UILabelo UIButton.

Dado que la resistencia a la compresión de contenido y las prioridades de abrazo de contenido de dos UIViewinstancias nuevas serán las mismas, y ninguna de las vistas proporciona un tamaño de contenido intrínseco, el motor de diseño debe adivinar qué tamaño debe asignarse a cada vista. En su caso, está asignando a la primera vista el 100% del espacio disponible, y nada a la segunda vista.

Si modifica su código para usar UILabelinstancias en su lugar, obtendrá mejores 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];

Tenga en cuenta que no es necesario crear explícitamente ninguna restricción usted mismo. Este es el principal beneficio del uso UIStackView: oculta los detalles (a menudo feos) de la gestión de restricciones del desarrollador.

Greg Brown
fuente
Tengo el mismo problema que esto funciona para las etiquetas, pero no para TextFields (o vistas, para el caso). También todos los tutoriales que encuentro usan etiquetas, imágenes o botones. ¿Significa esto que para algo diferente a estos elementos de la interfaz de usuario, no podemos usar este método?
fisicalattraction
@physicalattraction Las vistas que agregue a una vista de pila deben tener un tamaño de contenido intrínseco. Como recuerdo, el tamaño intrínseco de un campo de texto se basa en el contenido del campo (que es extraño). Las instancias en UIViewsí mismas no tienen un tamaño intrínseco. Es posible que deba aplicar restricciones adicionales a estas vistas para que la vista de pila sepa qué tan grande es hacerlas.
Greg Brown
Tiene sentido, así que lo estoy intentando. Ahora he construido una Vista en una xib con un campo de texto, que le doy una restricción de altura explícita de 30 píxeles. Además, hago las siguientes dos restricciones: MyTextField.top = top+20y bottom = MyTextField.bottom+20. Esperaría que esto le diera a mi punto de vista una altura intrínseca de 70, pero en cambio se queja de restricciones conflictivas. ¿Sabes lo que está pasando aquí? Es esta vista la que quiero colocar dentro de mi UIStackView.
physicalattraction
Creo que sé cuál es el problema. Una vista de pila fija automáticamente su subvista final arreglada a su borde inferior. Por lo tanto, la vista de pila intenta hacer que su contenedor vea el mismo tamaño que él. Sin embargo, le ha dicho a esta vista que debe tener 70 píxeles de alto, lo que crea un conflicto. Intente crear una vista vacía adicional inmediatamente después de la vista de contenedor y dele una prioridad de abrazo de contenido vertical de 0. Otorgue a su vista de contenedor una prioridad de abrazo de contenido vertical de 1000. Esto hará que la vista vacía se estire para llenar el espacio vacío en la pila. ver.
Greg Brown
Supongo que, por cierto, está utilizando una vista de pila vertical. También es posible que desee consultar este artículo que escribí sobre las vistas de pila: dzone.com/articles/…
Greg Brown
13

En 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
fuente
8

Tienes que establecer tu tipo de distribución. En su código, solo agregue:

self.stack1.distribution = UIStackViewDistributionFillEqually;

O puede configurar la distribución directamente en su generador de interfaces. Por ejemplo:

ingrese la descripción de la imagen aquí

Espero que ayude;) Lapinou.

Lapinou
fuente
8

Siguiendo dos líneas solucionó mi problema

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

Versión rápida -

    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
fuente
8

Para la respuesta aceptada cuando intenta ocultar cualquier vista dentro de la vista de pila, la restricción no funciona correctamente.

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

Razón es cuando la piel viewde stackViewesta forma se establece la altura de 0 a animarlo.

La solución cambia la restricción prioritycomo se muestra a continuación.

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

Mejorado en la respuesta por @Oleg Popov

Arunabh Das
fuente
4

En mi caso, lo que estaba jugando con lo que esperaba era que me faltaba esta línea:

stackView.translatesAutoresizingMaskIntoConstraints = false;

Después de eso, no es necesario establecer restricciones para mis subvistas organizadas, el stackview se encarga de eso.

PakitoV
fuente
4

Versión Swift 5 de la respuesta de Oleg Popov , que se basa en la respuesta del usuario 1046037

//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
fuente
1

Acabo de encontrar un problema muy similar. Tal como se mencionó anteriormente, las dimensiones de la vista de pila dependen de un tamaño de contenido intrínseco de las subvistas organizadas. Aquí está mi solución en Swift 2.xy la siguiente estructura de vista:

vista - UIView

customView - CustomView: UIView

stackView - UISTackView

subvistas organizadas - subclases de UIView personalizadas

    //: [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
fuente
-2

Prueba el siguiente código,

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. Avíseme si necesita más aclaraciones.

Nilesh Patel
fuente