Fügen Sie Ansichten in UIStackView programmgesteuert hinzu

158

Ich versuche, Ansichten in UIStackView programmgesteuert hinzuzufügen. Im Moment lautet mein Code:

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

Wenn ich die App bereitstelle, gibt es nur eine Ansicht und sie ist schwarz (Ansicht1 erhält auch die Parameter für Ansicht2).

Altimir Antonov
quelle
Sie haben Ihre Steckdose überprüft? Haben Sie die Unteransichten zur Laufzeit protokolliert?
Wain
10
Verwenden Sie addArrangedSubview:nichtaddSubview:
pkamb

Antworten:

244

Stapelansichten verwenden die intrinsische Inhaltsgröße. Verwenden Sie daher Layoutbeschränkungen, um die Abmessungen der Ansichten zu definieren.

Es gibt eine einfache Möglichkeit, Einschränkungen schnell hinzuzufügen (Beispiel):

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

Vollständiger Code:

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

Hinweis: Dies wurde unter iOS 9 getestet

UIStackView Equal Spacing (zentriert)

user1046037
quelle
26
Der Sinn der Verwendung von Stapelansichten besteht darin, dass sie die Details der Einschränkungsverwaltung vor dem Entwickler verbergen. Wie Sie bereits betont haben, hängt dies davon ab, dass die Unteransichten eine intrinsische Inhaltsgröße haben, was standardmäßig UIViewsnicht der Fall ist . Wenn das Originalplakat UILabelstattdessen Instanzen UIViewverwendet hätte, hätte sein Code wie erwartet funktioniert. Ich habe ein Beispiel hinzugefügt, das dies unten demonstriert.
Greg Brown
wenn ich das mache "[view1.heightAnchor ConstraintEqualToConstant: 100] .active = true;" Ich erhalte einen Fehler auf iOS 8. Es funktioniert nur auf iOS 9. Ich weiß, dass Uistackview für iOS 9+ ist, aber wie kann ich die HöheAncor-Einschränkung für
iOS
Mein StackView wird mithilfe des Storyboards hinzugefügt. Zur Laufzeit füge ich stackView mehrere UIViews hinzu (die andere Unteransichten enthalten). Nach dem Laden der Daten haben alle Ansichten in stackView dieselbe Höhe und werden nicht entsprechend dem Inhalt in der Größe geändert. @ user1046037
Mansuu ....
Haben Sie Einschränkungen für diese einzelnen Ansichten festgelegt, damit ihre Größe je nach Inhalt variiert?
user1046037
Dies ist der beste und optimale Weg, um Ansichten mit gleichen Breiten oder Höhen zu verwalten
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

Basierend auf @ user1046037 Antwort.

Oleg Popov
quelle
2
Ab iOS 8 ist es möglich, die Einschränkungen im Batch mit zu aktivieren. In der activate(_:)Regel ist dies effizienter. Developer.apple.com/documentation/uikit/nslayoutconstraint/…
Jonathan Cabrera
Swift 5 Version: stackoverflow.com/a/58827722/2273338
Abdurrahman Mubeen Ali
17

UIStackViewVerwendet interne Einschränkungen, um die angeordneten Unteransichten zu positionieren. Welche Einschränkungen erstellt werden, hängt davon ab, wie die Stapelansicht selbst konfiguriert ist. Standardmäßig erstellt eine Stapelansicht Einschränkungen, bei denen die angeordneten Unteransichten in einer horizontalen Linie angeordnet sind und die Vorder- und Nachansichten an den eigenen Vorder- und Hinterkanten befestigt werden. Ihr Code würde also ein Layout erzeugen, das folgendermaßen aussieht:

|[view1][view2]|

Der jeder Unteransicht zugewiesene Speicherplatz wird durch eine Reihe von Faktoren bestimmt, darunter die inhärente Inhaltsgröße der Unteransicht sowie deren Komprimierungsbeständigkeit und inhaltliche Prioritäten. Standardmäßig UIViewdefinieren Instanzen keine intrinsische Inhaltsgröße. Dies wird im Allgemeinen von einer Unterklasse wie UILabeloder bereitgestellt UIButton.

Da der Inhalt Komprimierungswiderstand und Inhalt umarmen Prioritäten von zwei neuen UIView Instanzen gleich sind und keine der beiden Ansichten eine intrinsische Inhaltsgröße bietet, muss die Layout-Engine nach bestem Wissen raten, welche Größe jeder Ansicht zugewiesen werden soll. In Ihrem Fall wird der ersten Ansicht 100% des verfügbaren Speicherplatzes zugewiesen, und der zweiten Ansicht wird nichts zugewiesen.

Wenn Sie Ihren Code so ändern, dass UILabelstattdessen Instanzen verwendet werden, erhalten Sie bessere Ergebnisse:

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

Beachten Sie, dass es nicht erforderlich ist, selbst explizit Einschränkungen zu erstellen. Dies ist der Hauptvorteil der Verwendung UIStackView- es verbirgt die (oft hässlichen) Details des Constraint-Managements vor dem Entwickler.

Greg Brown
quelle
Ich habe das gleiche Problem, dass dies für Labels funktioniert, aber nicht für TextFields (oder Views). Auch alle Tutorials, die ich finde, verwenden Beschriftungen, Bilder oder Schaltflächen. Bedeutet dies, dass wir diese Methode für nichts anderes als diese UI-Elemente nicht verwenden können?
Körperattraktion
@physicalattraction Die Ansichten, die Sie einer Stapelansicht hinzufügen, müssen eine intrinsische Inhaltsgröße haben. Wie ich mich erinnere, basiert die intrinsische Größe eines Textfelds auf dem Inhalt des Feldes (was ungerade ist). Instanzen von UIViewsich haben keine intrinsische Größe. Möglicherweise müssen Sie diesen Ansichten zusätzliche Einschränkungen zuweisen, damit die Stapelansicht weiß, wie groß sie sein müssen.
Greg Brown
Macht Sinn, also versuche ich es. Jetzt habe ich eine Ansicht in einer xib mit einem Textfeld erstellt, für die ich eine explizite Höhenbeschränkung von 30 Pixel gebe. Ich mache außerdem die folgenden zwei Einschränkungen: MyTextField.top = top+20und bottom = MyTextField.bottom+20. Ich würde erwarten, dass dies meiner Ansicht eine intrinsische Höhe von 70 geben würde, aber stattdessen beschwert es sich über widersprüchliche Einschränkungen. Weißt du was hier los ist? Diese Ansicht möchte ich in meine UIStackView einfügen.
Körperattraktion
Ich glaube ich weiß was das Problem ist. Eine Stapelansicht fixiert automatisch ihre endgültig angeordnete Unteransicht an ihrer Unterkante. Die Stapelansicht versucht also, Ihre Containeransicht auf die gleiche Größe wie sich selbst zu bringen. Sie haben dieser Ansicht jedoch mitgeteilt, dass sie 70 Pixel groß sein muss, was zu einem Konflikt führt. Versuchen Sie, unmittelbar nach Ihrer Containeransicht eine zusätzliche leere Ansicht zu erstellen, und geben Sie ihr eine vertikale Inhaltsumarmungspriorität von 0. Geben Sie Ihrer Containeransicht eine vertikale Inhaltsumarmungspriorität von 1000. Dadurch wird die leere Ansicht gedehnt, um den leeren Bereich im Stapel zu füllen Aussicht.
Greg Brown
Ich gehe übrigens davon aus, dass Sie eine vertikale Stapelansicht verwenden. Vielleicht möchten Sie auch diesen Artikel lesen, den
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)])
Ashim Dahal
quelle
8

Sie müssen Ihren Verteilungstyp festlegen. Fügen Sie in Ihrem Code Folgendes hinzu:

self.stack1.distribution = UIStackViewDistributionFillEqually;

Oder Sie können die Verteilung direkt in Ihrem Interface Builder festlegen. Beispielsweise:

Geben Sie hier die Bildbeschreibung ein

Hoffe das hilft;) Lapinou.

Lapinou
quelle
8

Die folgenden zwei Zeilen haben mein Problem behoben

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

Schnelle Version -

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

Für die akzeptierte Antwort, wenn Sie versuchen, eine Ansicht in der Stapelansicht auszublenden, funktioniert die Einschränkung nicht korrekt.

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

Grund ist , wenn die versteckt viewinstackView es wird die Höhe auf 0 gesetzt , es zu beleben.

Lösung Ändern Sie die Einschränkung prioritywie folgt.

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

Verbesserte Antwort von @Oleg Popov

Arunabh Das
quelle
4

In meinem Fall war das, was ich erwartet hatte, dass mir diese Zeile fehlte:

stackView.translatesAutoresizingMaskIntoConstraints = false;

Danach müssen keine Einschränkungen mehr für meine angeordneten Unteransichten festgelegt werden. Die Stapelansicht kümmert sich darum.

PakitoV
quelle
4

Swift 5- Version von Oleg Popovs Antwort , die auf der Antwort von user1046037 basiert

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

Ich bin gerade auf ein sehr ähnliches Problem gestoßen. Wie bereits erwähnt, hängen die Abmessungen der Stapelansicht von einer intrinsischen Inhaltsgröße der angeordneten Unteransichten ab. Hier ist meine Lösung in Swift 2.x und folgende Ansichtsstruktur:

Ansicht - UIView

customView - CustomView: UIView

stackView - UISTackView

arrangierte Unteransichten - benutzerdefinierte UIView-Unterklassen

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

Versuchen Sie unten 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];

Hoffe, es funktioniert. Bitte lassen Sie mich wissen, wenn Sie weitere Erläuterungen benötigen.

Nilesh Patel
quelle