Programowo dodaj widoki w UIStackView

158

Próbuję programowo dodawać widoki w UIStackView. Na razie mój kod to:

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

Kiedy wdrażam aplikację, jest tylko jeden widok i jest on w kolorze czarnym. (View1 pobierz również parametry dla view2)

Altimir Antonov
źródło
Sprawdziłeś swoje zdrowie psychiczne? Czy zarejestrowałeś podglądy w czasie wykonywania?
Wain
10
Użyj addArrangedSubview:, nieaddSubview:
pkamb

Odpowiedzi:

245

Widoki stosu używają wewnętrznego rozmiaru zawartości, więc użyj ograniczeń układu, aby zdefiniować wymiary widoków.

Istnieje prosty sposób na szybkie dodawanie ograniczeń (przykład):

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

Kompletny kod:

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

Uwaga: zostało to przetestowane na iOS 9

UIStackView Równe odstępy (wyśrodkowane)

user1046037
źródło
26
Celem korzystania z widoków stosu jest to, że ukrywają one przed programistą szczegóły zarządzania ograniczeniami. Jak zauważyłeś, zależy to od podglądów podrzędnych, które mają wewnętrzny rozmiar treści, który domyślnie UIViewsnie. Gdyby oryginalny plakat używał UILabelinstancji zamiast UIView, jego kod działałby zgodnie z oczekiwaniami. Dodałem przykład, który to demonstruje poniżej.
Greg Brown,
kiedy robię to "[view1.heightAnchor constraintEqualToConstant: 100] .active = true;" Otrzymuję błąd na ios 8, działa tylko na ios 9, wiem, że uistackview jest dla ios 9+, ale jak mogę ustawić ograniczenie wysokościAncor dla ios 8
nuridin selimko
Mój StackView jest dodawany za pomocą scenorysu. W czasie wykonywania dodaję kilka widoków UIV (zawierają inne widoki podrzędne) do stackView. Po załadowaniu danych wszystkie widoki wewnątrz stackView mają tę samą wysokość, nie są zmieniane w zależności od zawartości. @ user1046037
Mansuu ....
Czy masz ustawione ograniczenia dla tych poszczególnych widoków, aby ich rozmiar zmieniał się w zależności od zawartości?
user1046037
To najlepszy i optymalny sposób zarządzania widokami o równych szerokościach lub wysokościach
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

Na podstawie @ user1046037 answer.

Oleg Popov
źródło
2
Począwszy od iOS 8 , można aktywować ograniczenia w trybie wsadowym activate(_:)i zazwyczaj jest to bardziej efektywne w ten sposób developer.apple.com/documentation/uikit/nslayoutconstraint/ ...
Jonathan Cabrera
Wersja Swift 5: stackoverflow.com/a/58827722/2273338
Abdurrahman Mubeen Ali
17

UIStackViewużywa wewnętrznych ograniczeń do pozycjonowania swoich uporządkowanych widoków podrzędnych. To, jakie ograniczenia są tworzone, zależy od sposobu skonfigurowania samego widoku stosu. Domyślnie widok stosu tworzy wiązania, które układają jego rozmieszczone podwidoki w linii poziomej, przypinając widok wiodący i końcowy do własnych krawędzi wiodących i końcowych. Twój kod utworzyłby układ, który wygląda następująco:

|[view1][view2]|

Przestrzeń, która jest przydzielana do każdego widoku podrzędnego, jest określana przez szereg czynników, w tym wewnętrzny rozmiar treści podglądu podrzędnego oraz jego odporność na kompresję i priorytety obejmowania treści. Domyślnie UIViewinstancje nie definiują wewnętrznego rozmiaru zawartości. Jest to coś, co jest zwykle dostarczane przez podklasę, taką jak UILabellubUIButton .

Ponieważ odporność na kompresję treści i priorytety przytulania treści są dwa nowe UIView wystąpień będą takie same, a żaden widok nie zapewnia wewnętrznego rozmiaru zawartości, mechanizm układu musi jak najlepiej określić, jaki rozmiar powinien zostać przydzielony do każdego widoku. W twoim przypadku jest to przypisanie pierwszemu widokowi 100% dostępnej przestrzeni, a drugiemu nic.

Jeśli zmodyfikujesz swój kod, aby UILabelzamiast tego używał instancji, uzyskasz lepsze wyniki:

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

Zwróć uwagę, że nie jest konieczne samodzielne tworzenie żadnych ograniczeń. To jest główna zaleta używania UIStackView- ukrywa przed programistą (często brzydkie) szczegóły zarządzania ograniczeniami.

Greg Brown
źródło
Mam ten sam problem, który działa w przypadku etykiet, ale nie w przypadku pól tekstowych (lub widoków, jeśli o to chodzi). Również wszystkie samouczki, które znajduję, używają etykiet, obrazów lub przycisków. Czy to oznacza, że ​​w przypadku czegokolwiek innego niż te elementy interfejsu użytkownika nie możemy użyć tej metody?
fizyczne
@physicalattraction Widoki dodane do widoku stosu muszą mieć wewnętrzny rozmiar zawartości. O ile sobie przypominam, wewnętrzny rozmiar pola tekstowego zależy od jego zawartości (co jest dziwne). Instancje UIViewsame w sobie nie mają wewnętrznego rozmiaru. Może być konieczne zastosowanie dodatkowych ograniczeń do tych widoków, aby widok stosu wiedział, jak duże mają być one utworzone.
Greg Brown
Ma sens, więc próbuję. Teraz zbudowałem View in a xib z polem tekstowym, które daje wyraźne ograniczenie wysokości do 30 pikseli. Ponadto wprowadzam dwa ograniczenia: MyTextField.top = top+20i bottom = MyTextField.bottom+20. Spodziewałbym się, że dałoby to mojemu poglądowi wewnętrzną wysokość 70, ale zamiast tego narzeka na sprzeczne ograniczenia. Czy wiesz, co tu się dzieje? To jest ten widok, który chcę umieścić w moim UIStackView.
fizyczne
Myślę, że wiem, w czym jest problem. Widok stosu automatycznie przypina ostateczny ułożony widok podrzędny do dolnej krawędzi. Zatem widok stosu próbuje nadać kontenerowi taki sam rozmiar, jak on sam. Jednak powiedziałeś temu widokowi, że musi on mieć 70 pikseli wysokości, co powoduje konflikt. Spróbuj utworzyć dodatkowy pusty widok bezpośrednio po widoku kontenera i nadaj mu priorytet obejmujący zawartość pionową równy 0. Nadaj widokowi kontenera priorytet obejmujący zawartość pionową równy 1000. Spowoduje to rozciągnięcie pustego widoku, aby wypełnić puste miejsce w stosie widok.
Greg Brown
Przy okazji zakładam, że używasz widoku stosu pionowego. Możesz również zapoznać się z tym artykułem, który napisałem o widokach stosu: dzone.com/articles/ ...
Greg Brown
13

W 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
źródło
8

Musisz ustawić typ dystrybucji. W swoim kodzie wystarczy dodać:

self.stack1.distribution = UIStackViewDistributionFillEqually;

Możesz też ustawić dystrybucję bezpośrednio w programie do tworzenia interfejsów. Na przykład:

wprowadź opis obrazu tutaj

Mam nadzieję, że to pomoże;) Lapinou.

Lapinou
źródło
8

Kolejne dwie linijki rozwiązały mój problem

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

Wersja 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
źródło
8

W przypadku zaakceptowanej odpowiedzi, gdy próbujesz ukryć jakikolwiek widok w widoku stosu, ograniczenie nie działa poprawnie.

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

Powodem jest to, że kiedy hide the viewin stackView, ustawi wysokość na 0, aby go ożywić.

Rozwiązanie Zmień ograniczenie priorityjak poniżej.

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
źródło
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

Ulepszona odpowiedź przez @Oleg Popov

Arunabh Das
źródło
4

W moim przypadku bałam się, że spodziewałbym się, że brakuje mi tej linii:

stackView.translatesAutoresizingMaskIntoConstraints = false;

Po tym nie ma potrzeby ustawiania ograniczeń dla moich aranżowanych podglądów podrzędnych, załatwia to stackview.

PakitoV
źródło
4

Wersja Swift 5 odpowiedzi Olega Popova , oparta na odpowiedzi użytkownika1046037

//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
źródło
1

Właśnie natknąłem się na bardzo podobny problem. Jak wspomniano wcześniej, wymiary widoku stosu zależą od jednego wewnętrznego rozmiaru zawartości uporządkowanych widoków podrzędnych. Oto moje rozwiązanie w Swift 2.x i następująca struktura widoku:

widok - UIView

customView - CustomView: UIView

stackView - UISTackView

uporządkowane podglądy - niestandardowe podklasy 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 Chudzyński
źródło
-2

Wypróbuj poniższy kod,

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

Mam nadzieję, że to działa. Daj mi znać, jeśli potrzebujesz więcej wyjaśnień.

Nilesh Patel
źródło