Altimir Antonov
Altimir Antonov

Reputation: 5056

Add views in UIStackView programmatically

I'm trying to add views in UIStackView programmatically. For now My code is:

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

When i deploy the app, there is only 1 view and it is with black colour.(view1 get the parameters for view2 too)

Upvotes: 207

Views: 321187

Answers (17)

Nabeel ali
Nabeel ali

Reputation: 226

If you have want to add UIView at specific position, use this

yourStackView.insertArrangedSubview(yourView, at: index)

Upvotes: 2

Nilesh Patel
Nilesh Patel

Reputation: 6394

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

Upvotes: -1

SharonB9
SharonB9

Reputation: 1

func configureHorizontalView(){
    containerView.addSubview(horizontalStackView)
    _ = horizontalStackView.anchor(top: secondCurrencyTextField.bottomAnchor, 
                                   left: containerView.leftAnchor, 
                                   bottom: nil, 
                                   right: containerView.rightAnchor, 
                                   topConstant: 40, 
                                   leftConstant: 30, 
                                   bottomConstant: 0, 
                                   rightConstant: 30, 
                                   widthConstant: 0, 
                                   heightConstant: 65)
}

func configureFirstDropDownlabel(){
    //add a view to stackView with addArrangedSubview() 
    horizontalStackView.addArrangedSubview(firstDropDownlabel)
    _ = firstDropDownlabel.anchor(top: horizontalStackView.bottomAnchor, 
                                  left: horizontalStackView.leftAnchor, 
                                  bottom: nil, right: nil, 
                                  topConstant: 40, 
                                  leftConstant: 30, 
                                  bottomConstant: 0, 
                                  rightConstant: 0, 
                                  widthConstant: 0, 
                                  heightConstant: 0)
    firstDropDownlabel.widthAnchor.constraint(equalToConstant: 130).isActive = true
    firstDropDownlabel.heightAnchor.constraint(equalToConstant: 65).isActive = true
}

Upvotes: -1

Visal Rajapakse
Visal Rajapakse

Reputation: 2042

If you have many UIViews to add, you can use the following extension where you pass an array of UIViews where it will add them into the UIStackView by order

extension UIStackView {
    func addArrangedSubviews(_ subviews: [UIView]) {
        subviews.forEach{ self.addArrangedSubview($0) }
    }
}

Upvotes: 5

Barbara K
Barbara K

Reputation: 612

It is really not recommended to set a height constraint... If you can, never, never, never set a height! You need to check all the constraints of the views inside your UIStackView and be sure that there is constraints for bottom, top, leading and trailing. Someone said to me: it is like a guy pushing on walls. If he don't push on 4 sides, one of the wall will fall on him.

Upvotes: 0

Ekta
Ekta

Reputation: 101

Following two lines fixed my issue

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

Swift 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

Upvotes: 10

Ashim Dahal
Ashim Dahal

Reputation: 1147

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

Upvotes: 29

Ol Sen
Ol Sen

Reputation: 3348

Instead of coding all the constrains you could use a subclass that handles .intrinsicContentSize of UIView class in a simpler way. This solution improves also Interface Builder a little in a way to support with "intrinsicWidth" and "intrinsicHeight" of views. While you could extend UIView's and have those properties available on all UIViews in IB its cleaner to subclass.

// IntrinsicView.h
@import UIKit

IB_DESIGNABLE
@interface IntrinsicView : UIView
-(instancetype)initWithFrame:(CGRect)rect;
@property IBInspectable CGSize intrinsic;
@end
// IntrinsicView.m
#import "IntrinsicView.h"

@implementation IntrinsicView {
    CGSize _intrinsic;
}
- (instancetype)initWithFrame:(CGRect)frame {
    _intrinsic = frame.size;
    if ( !(self = [super initWithFrame:frame]) ) return nil;
    // your stuff here..
    return self;
}
-(CGSize)intrinsicContentSize {
    return _intrinsic;
}
-(void)prepareForInterfaceBuilder {
    self.frame = CGRectMake(self.frame.origin.x, self.frame.origin.y, _intrinsic.width,_intrinsic.height);
}
@end

Which means you can just allocate those IntrinsicView's and the self.frame.size is taken as intrinsicContentSize. That way it does not disturb the normal layout and you dont need to set constraint relations that don't even apply in full with UIStackViews

#import "IntrinsicView.h"

- (void)viewDidLoad {
    [super viewDidLoad];
    
    UIStackView *column = [[UIStackView alloc] initWithFrame:self.view.frame];
    column.spacing = 2;
    column.alignment = UIStackViewAlignmentFill;
    column.axis = UILayoutConstraintAxisVertical; //Up-Down
    column.distribution = UIStackViewDistributionFillEqually;
    
    for (int row=0; row<5; row++) {
        //..frame:(CGRect) defines here proportions and
        //relation to axis of StackView
        IntrinsicView *intrinsicView = [[IntrinsicView alloc] initWithFrame:CGRectMake(0, 0, 30.0, 30.0)];
        
        [column addArrangedSubview:intrinsicView];
    }
    [self.view addSubview:column];
}

now you can go crazy with UIStackView's enter image description here

or in swift + encoding, decoding, IB support, Objective-C support

@IBDesignable @objc class IntrinsicView : UIView {
    @IBInspectable var intrinsic : CGSize
    @objc override init(frame: CGRect) {
        intrinsic = frame.size
        super.init(frame: frame)
    }
    required init?(coder: NSCoder) {
        intrinsic = coder.decodeCGSize(forKey: "intrinsic")
        super.init(coder: coder)
    }
    override func encode(with coder: NSCoder) {
        coder.encode(intrinsic, forKey: "intrinsic")
        super.encode(with: coder)
    }
    override var intrinsicContentSize: CGSize {
        return intrinsic
    }
    override func prepareForInterfaceBuilder() {
        frame = CGRect(x: self.frame.origin.x, y: self.frame.origin.y, width: intrinsic.width, height: intrinsic.height)
    }
}

Upvotes: 0

Oleg Popov
Oleg Popov

Reputation: 2514

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

Based on @user1046037 answer.

Upvotes: 204

Abdurrahman Mubeen Ali
Abdurrahman Mubeen Ali

Reputation: 1341

Swift 5 version of Oleg Popov's answer, which is based on user1046037's answer

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

Upvotes: 5

PakitoV
PakitoV

Reputation: 2498

In my case the thing that was messing with I would expect was that I was missing this line:

stackView.translatesAutoresizingMaskIntoConstraints = false;

After that no need to set constraints to my arranged subviews whatsoever, the stackview is taking care of that.

Upvotes: 5

William Hu
William Hu

Reputation: 16151

For the accepted answer when you try to hide any view inside stack view, the constraint works not correct.

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

Reason is when hide the view in stackView it will set the height to 0 to animate it.

Solution change the constraint priority as below.

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
    }

}

Upvotes: 8

Janusz Chudzynski
Janusz Chudzynski

Reputation: 2710

I just came across very similar problem. Just like mentioned before the stack view's dimensions depend one intrinsic content size of the arranged subviews. Here is my solution in Swift 2.x and following view structure:

view - UIView

customView - CustomView:UIView

stackView - UISTackView

arranged subviews - custom UIView subclasses

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

Upvotes: 1

Greg Brown
Greg Brown

Reputation: 3254

UIStackView uses constraints internally to position its arranged subviews. Exactly what constraints are created depends on how the stack view itself is configured. By default, a stack view will create constraints that lay out its arranged subviews in a horizontal line, pinning the leading and trailing views to its own leading and trailing edges. So your code would produce a layout that looks like this:

|[view1][view2]|

The space that is allocated to each subview is determined by a number of factors including the subview's intrinsic content size and it's compression resistance and content hugging priorities. By default, UIView instances don't define an intrinsic content size. This is something that is generally provided by a subclass, such as UILabel or UIButton.

Since the content compression resistance and content hugging priorities of two new UIView instances will be the same, and neither view provides an intrinsic content size, the layout engine must make its best guess as to what size should be allocated to each view. In your case, it is assigning the first view 100% of the available space, and nothing to the second view.

If you modify your code to use UILabel instances instead, you will get better results:

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

Note that it is not necessary to explictly create any constraints yourself. This is the main benefit of using UIStackView - it hides the (often ugly) details of constraint management from the developer.

Upvotes: 22

Arunabh Das
Arunabh Das

Reputation: 14352

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

Improved on the answer by @Oleg Popov

Upvotes: 6

user1046037
user1046037

Reputation: 17685

Stack views use intrinsic content size, so use layout constraints to define the dimensions of the views.

There is an easy way to add constraints quickly (example):

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

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

Note: This was tested on iOS 9

UIStackView Equal Spacing (centered)

Upvotes: 293

Lapinou
Lapinou

Reputation: 1477

You have to set you distribution type. In your code, juste add:

self.stack1.distribution = UIStackViewDistributionFillEqually;

Or you can set the distribution directly in your interface builder. For example:

enter image description here

Hope that helps ;) Lapinou.

Upvotes: 10

Related Questions