Aggiungi visualizzazioni in UIStackView a livello di codice


158

Sto provando ad aggiungere visualizzazioni in UIStackView a livello di codice. Per ora il mio codice è:

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 distribuisco l'app, c'è solo 1 vista ed è di colore nero (view1 ottiene anche i parametri per view2)


La sanità mentale ha controllato il tuo punto vendita? Hai registrato le visualizzazioni secondarie in fase di esecuzione?
Guadagna il

10
Usa addArrangedSubview:, nonaddSubview:
pkamb il

Risposte:


245

Le viste in pila utilizzano dimensioni intrinseche del contenuto, quindi utilizzare i vincoli di layout per definire le dimensioni delle viste.

Esiste un modo semplice per aggiungere rapidamente i vincoli (esempio):

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

Codice 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: questo è stato testato su iOS 9

UIStackView Equal Spacing (centrato)


26
Il punto di usare le viste stack è che nascondono i dettagli della gestione dei vincoli dallo sviluppatore. Come hai sottolineato, questo si basa sul fatto che le visualizzazioni secondarie hanno una dimensione intrinseca del contenuto, che per impostazione predefinita UIViewsno. Se il poster originale avesse usato UILabelistanze anziché UIView, il suo codice avrebbe funzionato come previsto. Ho aggiunto un esempio che lo dimostra di seguito.
Greg Brown,

quando faccio questo "[view1.heightAnchor VincoloEqualToConstant: 100] .active = true;" sto ottenendo un errore su iOS 8. Funziona solo su iOS 9. So che uistackview è per iOS 9+, ma come posso impostare l'altezza Vincolo dincora per
iOS

Il mio StackView viene aggiunto utilizzando lo storyboard. In fase di esecuzione sto aggiungendo diverse UIVview (contengono altre subview) a StackView. Dopo aver caricato i dati, tutte le viste all'interno di stackView hanno la stessa altezza, non vengono ridimensionate in base al contenuto. @ user1046037
Mansuu ....

Hai vincoli impostati per quelle singole viste in modo che le loro dimensioni possano variare in base al contenuto?
user1046037

Questo è il modo migliore e ottimale per gestire le viste con uguale larghezza o altezza
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

Basato sulla risposta @ user1046037.


2
A partire da iOS 8 , è possibile attivare i vincoli in batch utilizzando activate(_:), ed è generalmente più efficiente farlo in questo modo developer.apple.com/documentation/uikit/nslayoutconstraint/…
Jonathan Cabrera il


17

UIStackViewutilizza i vincoli internamente per posizionare le viste secondarie organizzate. Esattamente quali vincoli vengono creati dipende da come è configurata la vista stack. Per impostazione predefinita, una vista in pila creerà vincoli che dispongono le viste secondarie organizzate in una linea orizzontale, fissando le viste in testa e in coda ai propri bordi in entrata e in uscita. Quindi il tuo codice produrrebbe un layout simile al seguente:

|[view1][view2]|

Lo spazio assegnato a ciascuna sottoview è determinato da una serie di fattori, tra cui la dimensione intrinseca del subview e la sua resistenza alla compressione e le priorità di abbracciamento del contenuto. Per impostazione predefinita, le UIViewistanze non definiscono una dimensione intrinseca del contenuto. Questo è qualcosa che viene generalmente fornito da una sottoclasse, come UILabelo UIButton.

Poiché la resistenza alla compressione del contenuto e le priorità di abbracciamento del contenuto di due nuove UIViewistanze saranno le stesse e nessuna delle due viste fornisce una dimensione intrinseca del contenuto, il motore di layout deve fare la migliore ipotesi sulla dimensione da assegnare a ciascuna vista. Nel tuo caso, sta assegnando la prima vista al 100% dello spazio disponibile e nulla alla seconda vista.

Se modifichi il codice per utilizzare UILabelinvece le istanze, otterrai risultati migliori:

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

Si noti che non è necessario creare esplicitamente alcun vincolo. Questo è il principale vantaggio dell'utilizzo UIStackView: nasconde allo sviluppatore i dettagli (spesso brutti) della gestione dei vincoli.


Ho lo stesso problema che funziona con le etichette, ma non con TextFields (o Views, per quella materia). Anche tutti i tutorial che trovo usano etichette, immagini o pulsanti. Questo significa che per qualcosa di diverso da questi elementi dell'interfaccia utente, non possiamo usare questo metodo?
fisica il

@physicalattraction Le viste che aggiungi a una vista pila devono avere una dimensione intrinseca del contenuto. Ricordo che la dimensione intrinseca di un campo di testo si basa sul contenuto del campo (che è dispari). Le istanze di per UIViewsé non hanno una dimensione intrinseca. Potrebbe essere necessario applicare ulteriori vincoli a queste viste in modo che la vista dello stack sappia quanto sia grande renderle.
Greg Brown,

Ha senso, quindi lo sto provando. Ora ho creato una vista in un xib con un campo di testo, che conferisco un vincolo di altezza esplicita di 30 pixel. Faccio inoltre i seguenti due vincoli: MyTextField.top = top+20e bottom = MyTextField.bottom+20. Mi aspetterei che ciò darebbe al mio punto di vista un'altezza intrinseca di 70, ma invece si lamenta di vincoli contrastanti. Sai cosa sta succedendo qui? È questa vista che voglio inserire nel mio UIStackView.
fisica il

Penso di sapere qual è il problema. Una vista pila impila automaticamente la vista secondaria organizzata finale sul bordo inferiore. Quindi la vista dello stack sta cercando di far visualizzare al tuo contenitore le stesse dimensioni di se stesso. Tuttavia, hai detto a questo punto di vista che deve essere alto 70 pixel, il che crea un conflitto. Prova a creare una vista vuota aggiuntiva immediatamente dopo la vista del contenitore e assegnagli una priorità di abbraccio di contenuto verticale pari a 0. Assegna al tuo contenitore una priorità di abbraccio di contenuto verticale di 1000. Ciò causerà un allungamento della vista vuota per riempire lo spazio vuoto nella pila Visualizza.
Greg Brown,

Ad ogni modo, suppongo che tu stia usando una vista pila verticale. Puoi anche dare un'occhiata a questo articolo che ho scritto sulle visualizzazioni di stack: dzone.com/articles/…
Greg Brown,

13

In 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)])

8

Devi impostare il tipo di distribuzione. Nel tuo codice, juste aggiungi:

self.stack1.distribution = UIStackViewDistributionFillEqually;

Oppure puoi impostare la distribuzione direttamente nel tuo builder di interfacce. Per esempio:

inserisci qui la descrizione dell'immagine

Spero che aiuti;) Lapinou.


8

Seguire due righe risolto il mio problema

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

Versione rapida -

    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

8

Per la risposta accettata quando si tenta di nascondere una vista all'interno della vista pila, il vincolo non funziona correttamente.

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

Il motivo è quando nascondi viewin stackViewesso si imposta l'altezza su 0 per animarlo.

La soluzione modifica il vincolo prioritycome di seguito.

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
    }

}

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

Migliorato sulla risposta di @Oleg Popov


4

Nel mio caso, ciò che mi aspettavo era che mi mancasse questa riga:

stackView.translatesAutoresizingMaskIntoConstraints = false;

Dopodiché non è necessario impostare vincoli per le mie sottoview organizzate, lo stackview se ne occupa.


4

Versione Swift 5 della risposta di Oleg Popov , che si basa sulla risposta dell'utente1046037

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

1

Mi sono appena imbattuto in un problema molto simile. Proprio come menzionato prima, le dimensioni della vista dello stack dipendono da una dimensione del contenuto intrinseco delle viste secondarie organizzate. Ecco la mia soluzione in Swift 2.x e la seguente struttura di visualizzazione:

view - UIView

customView - CustomView: UIView

stackView - UISTackView

visualizzazioni organizzate - sottoclassi personalizzate 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

-2

Prova sotto il codice,

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

Spero funzioni. Per favore fatemi sapere se avete bisogno di ulteriori chiarimenti.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.