Ajouter des vues dans UIStackView par programmation

158

J'essaye d'ajouter des vues dans UIStackView par programme. Pour l'instant mon code est:

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

Lorsque je déploie l'application, il n'y a qu'une seule vue et elle est de couleur noire. (View1 récupère également les paramètres pour view2)

Altimir Antonov
la source
Vous avez vérifié votre point de vente? Avez-vous enregistré les sous-vues lors de l'exécution?
Wain
10
Utilisez addArrangedSubview:, pasaddSubview:
pkamb

Réponses:

245

Les vues de pile utilisent la taille du contenu intrinsèque, utilisez donc des contraintes de mise en page pour définir les dimensions des vues.

Il existe un moyen simple d'ajouter rapidement des contraintes (exemple):

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

Code complet:

- (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;
}

Remarque: cela a été testé sur iOS 9

Espacement égal UIStackView (centré)

utilisateur1046037
la source
26
L'intérêt d'utiliser les vues de pile est qu'elles cachent les détails de la gestion des contraintes au développeur. Comme vous l'avez souligné, cela repose sur les sous-vues ayant une taille de contenu intrinsèque, ce qui UIViewsn'est pas le cas par défaut . Si l'affiche d'origine avait utilisé des UILabelinstances au lieu de UIView, son code aurait fonctionné comme il s'y attendait. J'ai ajouté un exemple qui le démontre ci-dessous.
Greg Brown
quand je fais cela "[view1.heightAnchor constraintEqualToConstant: 100] .active = true;" Je reçois une erreur sur ios 8.Il ne fonctionne que sur ios 9.Je sais que uistackview est pour ios 9+ mais comment puis-je définir la contrainte heightAncor pour ios 8
nuridin selimko
Mon StackView est ajouté à l'aide du storyboard. Au moment de l'exécution, j'ajoute plusieurs UIViews (contenant d'autres sous-vues) à stackView. Après le chargement des données, toutes les vues à l'intérieur de stackView ont la même hauteur, elles ne sont pas redimensionnées en fonction du contenu. @ user1046037
Mansuu ....
Avez-vous défini des contraintes pour ces vues individuelles afin que leur taille varie en fonction du contenu?
user1046037
C'est le meilleur moyen optimal de gérer des vues avec des largeurs ou des hauteurs égales
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

Basé sur la réponse @ user1046037.

Oleg Popov
la source
2
À partir d' iOS 8 , il est possible d'activer les contraintes par lots en utilisant activate(_:), et il est généralement plus efficace de le faire de cette façon developer.apple.com/documentation/uikit/nslayoutconstraint/…
Jonathan Cabrera
Version Swift 5: stackoverflow.com/a/58827722/2273338
Abdurrahman Mubeen Ali
17

UIStackViewutilise des contraintes en interne pour positionner ses sous-vues organisées. Les contraintes exactement créées dépendent de la configuration de la vue de pile elle-même. Par défaut, une vue de pile crée des contraintes qui présentent ses sous-vues disposées sur une ligne horizontale, épinglant les vues avant et arrière à ses propres bords avant et arrière. Ainsi, votre code produirait une mise en page qui ressemble à ceci:

|[view1][view2]|

L'espace alloué à chaque sous-vue est déterminé par un certain nombre de facteurs, y compris la taille du contenu intrinsèque de la sous-vue, sa résistance à la compression et ses priorités de respect du contenu. Par défaut, les UIViewinstances ne définissent pas une taille de contenu intrinsèque. C'est quelque chose qui est généralement fourni par une sous-classe, telle que UILabelou UIButton.

Depuis la résistance à la compression du contenu et les priorités de respect du contenu de deux nouveaux UIView instances seront les mêmes, et qu'aucune vue ne fournit une taille de contenu intrinsèque, le moteur de mise en page doit faire sa meilleure estimation de la taille à allouer à chaque vue. Dans votre cas, il attribue à la première vue 100% de l'espace disponible et rien à la deuxième vue.

Si vous modifiez votre code pour utiliser des UILabelinstances à la place, vous obtiendrez de meilleurs résultats:

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

Notez qu'il n'est pas nécessaire de créer vous-même explicitement des contraintes. C'est le principal avantage de l'utilisation UIStackView- cela masque les détails (souvent laids) de la gestion des contraintes au développeur.

Greg Brown
la source
J'ai le même problème que cela fonctionne pour les étiquettes, mais pas pour TextFields (ou Views, d'ailleurs). De plus, tous les didacticiels que je trouve utilisent des étiquettes, des images ou des boutons. Cela signifie-t-il que pour quelque chose de différent de ces éléments d'interface utilisateur, nous ne pouvons pas utiliser cette méthode?
physicalattraction
@physicalattraction Les vues que vous ajoutez à une vue de pile doivent avoir une taille de contenu intrinsèque. Si je me souviens bien, la taille intrinsèque d'un champ de texte est basée sur le contenu du champ (ce qui est étrange). Les instances en UIViewsoi n'ont pas de taille intrinsèque. Vous devrez peut-être appliquer des contraintes supplémentaires à ces vues pour que la vue de pile sache quelle taille les créer.
Greg Brown
Cela a du sens, alors je l’essaye. Maintenant, j'ai construit une vue dans un xib avec un champ de texte, auquel je donne une contrainte de hauteur explicite de 30 pixels. Je fais en outre les deux contraintes suivantes: MyTextField.top = top+20et bottom = MyTextField.bottom+20. Je m'attendrais à ce que cela donne à mon avis une hauteur intrinsèque de 70, mais au lieu de cela, il se plaint de contraintes conflictuelles. Savez-vous ce qui se passe ici? C'est cette vue que je souhaite placer dans mon UIStackView.
attraction physique
Je pense savoir quel est le problème. Une vue de pile épingle automatiquement sa sous-vue arrangée finale à son bord inférieur. Ainsi, la vue de la pile essaie de donner à votre conteneur la même taille que lui-même. Cependant, vous avez indiqué à cette vue qu'elle devait mesurer 70 pixels de haut, ce qui crée un conflit. Essayez de créer une vue vide supplémentaire immédiatement après la vue de votre conteneur et attribuez-lui une priorité verticale de couverture de contenu de 0. Donnez à votre vue de conteneur une priorité de contenu vertical de 1000. Cela entraînera l'étirement de la vue vide pour remplir l'espace vide de la pile. vue.
Greg Brown
Je suppose que vous utilisez une vue de pile verticale en passant. Vous pouvez également consulter cet article que j'ai écrit sur les vues de la pile: dzone.com/articles
Greg Brown
13

Dans 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
la source
8

Vous devez définir votre type de distribution. Dans votre code, ajoutez simplement:

self.stack1.distribution = UIStackViewDistributionFillEqually;

Ou vous pouvez définir la distribution directement dans votre générateur d'interface. Par exemple:

entrez la description de l'image ici

J'espère que ça aide;) Lapinou.

Lapinou
la source
8

Suivre deux lignes a résolu mon problème

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

Version 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
la source
8

Pour la réponse acceptée lorsque vous essayez de masquer une vue dans la vue de pile, la contrainte ne fonctionne pas correctement.

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

La raison est quand la peau viewdans stackViewce sera régler la hauteur à 0 pour l' animer.

Solution modifiez la contrainte prioritycomme ci-dessous.

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
la source
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

Amélioration de la réponse par @Oleg Popov

Arunabh Das
la source
4

Dans mon cas, le problème auquel je m'attendais était que je manquais cette ligne:

stackView.translatesAutoresizingMaskIntoConstraints = false;

Après cela, plus besoin de définir des contraintes sur mes sous-vues organisées, la stackview s'en charge.

PakitoV
la source
4

Version Swift 5 de la réponse d' Oleg Popov , basée sur la réponse 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
la source
1

Je viens de rencontrer un problème très similaire. Tout comme mentionné précédemment, les dimensions de la vue de pile dépendent d'une taille de contenu intrinsèque des sous-vues organisées. Voici ma solution dans Swift 2.x et la structure de vue suivante:

vue - UIView

customView - CustomView: UIView

stackView - UISTackView

sous-vues organisées - sous-classes UIView personnalisées

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

Essayez ci-dessous le code,

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

Esperons que ça marche. Veuillez me faire savoir si vous avez besoin de plus de précisions.

Nilesh Patel
la source