以编程方式在UIStackView中添加视图

204

我正在尝试通过编程方式向UIStackView添加视图。 目前我的代码是:

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

当我部署这个应用程序时,只有一个视图,并且它是黑色的。(view1也获取了view2的参数)


你检查了插座的健全性吗?你在运行时记录了子视图吗? - Wain
25
使用addArrangedSubview:而不是addSubview: - pkamb
17个回答

292

堆栈视图使用内在内容大小,因此请使用布局约束来定义视图的尺寸。

有一种快速添加约束的简单方法(示例):

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

完整代码:

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

注意:这是在iOS 9上测试的。

UIStackView等间距(居中)


35
使用堆栈视图的重点在于将约束管理的细节从开发者中隐藏。你提到了这一点,这依赖于子视图具有内在内容大小,而默认的 UIViews 不具备此特性。如果原帖作者使用 UILabel 实例而不是 UIView,那么他的代码将按预期工作。我在下面添加了一个演示示例。 - Greg Brown
当我执行这个代码 "[view1.heightAnchor constraintEqualToConstant:100].active = true;" 时,在iOS 8上会出现错误。它只能在iOS 9上工作。我知道UIStackView是为iOS 9+设计的,但我该如何在iOS 8上设置heightAnchor约束呢? - nuridin selimko
我的StackView是使用Storyboard添加的。在运行时,我向stackView添加了几个UIView(包含其他子视图)。加载数据后,stackView内的所有视图都具有相同的高度,它们不会根据内容进行调整大小。@user1046037 - Mansuu....
你是否为这些单独的视图设置了约束条件,以便它们的大小根据内容而变化? - user1046037
这是管理具有相等宽度或高度的视图的最佳和最优方式。 - Kampai
Swift 5 版本:https://dev59.com/bl0a5IYBdhLWcg3wFlRG#58827722 - Abdurrahman Mubeen Ali

199

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

基于 @user1046037 的答案。


3
从iOS 8开始,可以使用activate(_:)一次性激活多个约束条件,通常使用这种方式效率更高。https://developer.apple.com/documentation/uikit/nslayoutconstraint/1526955-activate - Jonathan Cabrera
Swift 5 版本:https://dev59.com/bl0a5IYBdhLWcg3wFlRG#58827722 - Abdurrahman Mubeen Ali

26

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

非常好的例子,谢谢 +1 - Vicky

22

UIStackView 内部使用约束来定位其排列的子视图。创建的确切约束取决于 stack view 自身的配置方式。默认情况下,stack view 将创建将其排列的子视图固定在其前缘和后缘上,以水平线布置的约束。因此,您的代码将生成如下所示的布局:

|[view1][view2]|
每个子视图分配的空间取决于多个因素,包括子视图的内在内容大小、抗压缩和内容吸附优先级。默认情况下,UIView 实例不定义内在内容大小,这通常由子类(如 UILabelUIButton)提供。
由于两个新的 UIView 实例的内容抗压缩和内容吸附优先级相同,且两个视图都未提供内在内容大小,布局引擎必须尽力猜测应为每个视图分配的大小。在您的情况下,它将100%的可用空间分配给第一个视图,并将第二个视图分配为零。
如果您修改代码以使用UILabel实例,则会获得更好的结果:
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];

请注意,并不需要显式地创建任何约束条件。这是使用 UIStackView 的主要好处 - 它将约束管理(通常很丑陋)的细节从开发者身上隐藏起来。


我有同样的问题,这种方法适用于标签,但不适用于文本字段(或视图)。此外,我找到的所有教程都使用标签、图像或按钮。这是否意味着对于与这些 UI 元素不同的任何东西,我们都不能使用这种方法? - physicalattraction
@physicalattraction:您添加到堆栈视图中的视图必须具有内在内容大小。据我回忆,文本字段的内在大小基于字段的内容(这很奇怪)。UIView 的实例本身没有内在大小。您可能需要对这些视图应用其他约束条件,以便堆栈视图知道如何使它们变大。 - Greg Brown
有道理,所以我正在尝试它。现在我已经在xib中构建了一个视图,并给出了一个30像素的显式高度约束的文本字段。我还制定了以下两个约束条件:MyTextField.top = top+20bottom = MyTextField.bottom+20。我原以为这会给我的视图一个内在高度为70,但实际上它抱怨存在冲突的约束条件。你知道这里发生了什么吗?我想把这个视图放在我的UIStackView中。 - physicalattraction
我认为我知道问题所在。堆栈视图会自动将最终排列的子视图固定到其底部边缘。因此,堆栈视图试图使您的容器视图与自身大小相同。但是,您已告诉该视图它必须高70像素,这就创建了冲突。尝试在容器视图之后立即创建一个额外的空视图,并将其垂直内容吸附优先级设置为0。将容器视图的垂直内容吸附优先级设置为1000。这将使空视图拉伸以填充堆栈视图中的空白空间。 - Greg Brown
顺便说一下,我假设您正在使用垂直堆栈视图。您可能还想查看我撰写的有关堆栈视图的文章:https://dzone.com/articles/building-responsive-ios-applications-using-markup - Greg Brown
谢谢,我会在本周晚些时候查看这篇文章。关于冲突约束的抱怨仅限于UIView,甚至没有将它们放置在StackView中。我注意到,我可以通过降低三个约束中的两个的优先级来缓解Xcode的压力,但我仍然在努力使其正确。 - physicalattraction

10

您需要设置分布类型。在您的代码中,只需添加:

self.stack1.distribution = UIStackViewDistributionFillEqually;

或者,您可以直接在界面生成器中设置分布。

例如:

输入图像描述

希望这有所帮助;)
Lapinou。


10

以下两行代码解决了我的问题

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

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

8

对于被接受的答案,当您尝试隐藏堆栈视图中的任何视图时,约束不起作用。

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

原因:隐藏stackView中的view时,会将其高度设置为0以进行动画效果。

解决方法:按以下方式更改约束的优先级。

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

在Oleg Popov的答案基础上做了改进


5
在我的情况下,让我感到困扰的是我漏掉了这一行代码:
stackView.translatesAutoresizingMaskIntoConstraints = false;

之后就不需要对我安排的子视图设置任何限制了,因为堆栈视图会处理这一切。


5

Swift 5版的Oleg Popov的回答,基于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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接