Добавить представления в UIStackView программно

158

Я пытаюсь добавить представления в 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];

При развертывании приложения отображается только 1 вид, и он черного цвета. (View1 также получает параметры для view2).

Альтимир Антонов
источник
Вы в здравом уме проверили вашу торговую точку? Вы регистрировали подпредставления во время выполнения?
Wain
10
Используй addArrangedSubview:, неaddSubview:
пкамб

Ответы:

245

Представления стека используют собственный размер содержимого, поэтому используйте ограничения макета для определения размеров представлений.

Существует простой способ быстро добавить ограничения (пример):

[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 Равный интервал (по центру)

user1046037
источник
26
Смысл использования стековых представлений заключается в том, что они скрывают детали управления ограничениями от разработчика. Как вы указали, это зависит от подпредставлений, имеющих собственный размер контента, который по умолчанию UIViewsне имеет. Если бы оригинальный плакат использовал UILabelэкземпляры вместо UIView, его код работал бы так, как он ожидал. Я добавил пример, который демонстрирует это ниже.
Грег Браун
когда я делаю это "[view1.heightAnchor constraintEqualToConstant: 100] .active = true;" я получаю ошибку на ios 8. Он работает только на ios 9. Я знаю, что uistackview для ios 9+, но как мне установить ограничение heightAncor для ios 8
nuridin selimko
Мой StackView добавлен с использованием раскадровки. Во время выполнения я добавляю несколько UIViews (содержат другие подпредставления) в stackView. После загрузки данных все представления внутри stackView имеют одинаковую высоту, они не меняются в соответствии с содержимым. @ user1046037
Mansuu ....
У вас установлены ограничения для этих отдельных представлений, чтобы их размер варьировался в зависимости от содержимого?
user1046037
Это лучший и оптимальный способ управления видами с одинаковой шириной или высотой
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

Основано на ответе @ user1046037.

Олег Попов
источник
2
Начиная с iOS 8 , можно активировать ограничения в пакетном режиме activate(_:), и, как правило, это более эффективно сделать так. Developer.apple.com/documentation/uikit/nslayoutconstraint/…
Джонатан Кабрера
Версия Swift 5: stackoverflow.com/a/58827722/2273338
Абдуррахман Мубин Али
17

UIStackViewиспользует внутренние ограничения для позиционирования своих упорядоченных подпредставлений. Какие именно ограничения создаются, зависит от того, как настроено само представление стека. По умолчанию представление стека создает ограничения, которые размещают его упорядоченные подпредставления в горизонтальной линии, закрепляя передние и задние представления своими собственными передними и задними краями. Таким образом, ваш код будет производить макет, который выглядит следующим образом:

|[view1][view2]|

Пространство, которое выделяется для каждого подпредставления, определяется рядом факторов, включая собственный размер контента подпредставления, его устойчивость к сжатию и приоритеты объятия контента. По умолчанию UIViewэкземпляры не определяют внутренний размер контента. Это то, что обычно предоставляется подклассом, таким как UILabelили UIButton.

Так как сопротивление сжатию контента и контент объятия приоритетов двух новых 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- оно скрывает (часто некрасивые) детали управления ограничениями от разработчика.

Грег Браун
источник
У меня та же проблема, что это работает для меток, но не для текстовых полей (или просмотров, в этом отношении). Также все учебники, которые я нахожу, используют Ярлыки, Изображения или Кнопки. Означает ли это, что для чего-либо отличного от этих элементов пользовательского интерфейса мы не можем использовать этот метод?
физическая
@physicalattraction Представления, добавляемые в представление стека, должны иметь внутренний размер содержимого. Насколько я помню, внутренний размер текстового поля основан на содержимом поля (что нечетно). Экземпляры UIViewсами по себе не имеют собственного размера. Возможно, вам понадобится применить дополнительные ограничения к этим представлениям, чтобы представление стека знало, насколько велико их создание.
Грег Браун
Имеет смысл, поэтому я пытаюсь это сделать. Теперь я построил View в xib с текстовым полем, которое я дал явное ограничение по высоте в 30 пикселей. Кроме того, я делаю следующие два ограничения: MyTextField.top = top+20и bottom = MyTextField.bottom+20. Я ожидал бы, что это даст моему взгляду внутреннюю высоту 70, но вместо этого он жалуется на противоречивые ограничения. Вы знаете, что здесь происходит? Именно это представление я хочу разместить внутри своего UIStackView.
физическая
Я думаю, я знаю, в чем проблема. Представление стека автоматически прикрепляет свое окончательно упорядоченное подпредставление к его нижнему краю. Таким образом, представление стека пытается сделать так, чтобы ваш контейнер отображал тот же размер, что и он сам. Однако вы сказали, что это должно быть 70 пикселей в высоту, что создает конфликт. Попробуйте создать дополнительное пустое представление сразу после представления контейнера и присвоить ему приоритет обтекания вертикального содержимого, равный 0. Присвойте виду контейнера вертикальный приоритет обхвата содержимого 1000. Это приведет к растяжению пустого представления, чтобы заполнить пустое пространство в стеке. Посмотреть.
Грег Браун
Я предполагаю, что вы используете вертикальный вид стека, кстати. Вы также можете проверить эту статью, которую я написал о представлениях стека: dzone.com/articles/…
Грег Браун,
13

В 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

Вы должны установить свой тип распространения. В вашем коде просто добавьте:

self.stack1.distribution = UIStackViewDistributionFillEqually;

Или вы можете установить дистрибутив прямо в вашем конструкторе интерфейсов. Например:

введите описание изображения здесь

Надеюсь, это поможет;) Lapinou.

Lapinou
источник
8

Следующие две строки исправили мою проблему

    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
Ekta
источник
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)>"
)

Причина в том, что при скрытии viewв stackViewнем будет установлено значение 0 для его анимации.

Решение изменить ограничение, priorityкак показано ниже.

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

Улучшено в ответ @ Олег Попов

Арунабх Дас
источник
4

В моём случае я ожидал, что мне не хватит этой строки:

stackView.translatesAutoresizingMaskIntoConstraints = false;

После этого нет необходимости устанавливать ограничения для моих упорядоченных подпредставлений, стековое представление позаботится об этом.

PakitoV
источник
4

Swift 5 версия ответа Олега Попова , основанная на ответе пользователя 1046037

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

Я только столкнулся с очень похожей проблемой. Как упоминалось ранее, размеры стекового представления зависят от размера внутреннего содержимого упорядоченных подпредставлений. Вот мое решение в Swift 2.x и следующая структура представления:

вид - UIView

customView - CustomView: UIView

stackView - UISTackView

упорядоченные подпредставления - пользовательские подклассы 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

Попробуйте код ниже,

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

Надеюсь, что это работает. Пожалуйста, дайте мне знать, если вам нужно больше разъяснений.

Нилеш Патель
источник