0
let N = 4 
let holder = UIView(frame: CGRectMake(0, 0, someWidth, N * 100))
for var i = 0; i <= N; i++ {
    let content = UIView(frame: CGRect.zero) //height should be 100
    content.backgroundColor = randomColor()
    content.translatesAutoresizingMaskIntoConstraints = true
    holder.addSubview(content)
}

The above code will add N UIViews to holder. I would like to programmatically use NSLayoutConstraints to display the content vertically, one on top of each other.

Each content should have height of 100. Each content should have constraints on all 4 edges. The first content should have top/leading/trailing constraints (constant=0) to the holder. The last content should have bottom/leading/trailing constraints (constant=0) to holder.

The rest of the content should have top/bottom constraints (constant=8) to the other sibling content.

How do I create these constraints?

TIMEX
  • 259,804
  • 351
  • 777
  • 1,080

3 Answers3

1

You can do something like,

   let content = UIView()
    content.backgroundColor = UIColor.redColor()
    content.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(content)


    let topConstraint = NSLayoutConstraint(item: content, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
    view.addConstraint(topConstraint)

    let leadingConstraint = NSLayoutConstraint(item: content, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
    view.addConstraint(leadingConstraint)

    let bottomConstraint = NSLayoutConstraint(item: content, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
    view.addConstraint(bottomConstraint)

 let trailingConstraint = NSLayoutConstraint(item: content, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
    view.addConstraint(trailingConstraint)        

    let heightConstraint = NSLayoutConstraint(item: content, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: 100)
    view.addConstraint(heightConstraint)

Here you should consider holder instead of view.

This constrains are for middle views. for first and last view scenario should be different something like,

  //Top constraint for first view should be like,

   let topConstraintForfirstView = NSLayoutConstraint(item: content, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
    view.addConstraint(topConstraintForfirstView)

  //Bottom constraint for last view should be like,

   let bottomConstraintForLastview = NSLayoutConstraint(item: content, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
    view.addConstraint(bottomConstraintForLastview)

You can manage space or distance by changing constant of constraint. if you want 20 pixel vertical spacing between views then your constant of top constraints should be 20.

You can refer this answer as reference.

Don't give height and bottom both. If you give fix height then you not require bottom and if you give bottom then you not require fix height.

hope this will help :)

Community
  • 1
  • 1
Ketan Parmar
  • 27,092
  • 9
  • 50
  • 75
1

Vertical page

Samething I had done for the horizontal page. So here is code

var scrollView = UIScrollView()
var contectView = UIView();

Add SubView of holderview

    self.scrollView.translatesAutoresizingMaskIntoConstraints = false;
    self.view.addSubview(self.scrollView);

    self.contectView.translatesAutoresizingMaskIntoConstraints = false;
    self.scrollView.addSubview(self.contectView);

Create constraint for holderView

 let views = ["contectView" : self.contectView , "scrollview" : self.scrollView ];

    // create constraint for scrollview
    self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollview]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
    self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollview]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))

    // create constraint for contectview with low priority for width and height
    self.scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[contectView(==scrollview@250)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))
    self.scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[contectView(==scrollview@250)]|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views))

Make horizontal string constraint

     var verticalConstraintsFormat = "";  // create horizantal constraint string
    let subViews = NSMutableDictionary();
    subViews["scrollView"] = self.scrollView;

Create dynamic page

 let totalPage : NSInteger = 10;

    for i in 0...totalPage {

        // Create your page here
        let objProductView  = UIView()
        objProductView.translatesAutoresizingMaskIntoConstraints = false;
        objProductView.backgroundColor = UIColor(white: CGFloat(arc4random()%100)/CGFloat(100), alpha: 1)
        self.contectView.addSubview(objProductView);

        let key = NSString(format: "productView%d", i) as  NSString
        subViews[key] = objProductView;


        if(i==0){
            verticalConstraintsFormat = verticalConstraintsFormat.stringByAppendingFormat("V:|-0-[%@(==100)]", key);
        }
        else if (i==totalPage) {
            verticalConstraintsFormat = verticalConstraintsFormat.stringByAppendingFormat("-0-[%@(==100)]-0-|", key);

        }
        else{
            verticalConstraintsFormat = verticalConstraintsFormat.stringByAppendingFormat("-0-[%@(==100)]", key);

        }

        let verticalConstraintsFormat1 = NSString(format:"H:|[%@(==scrollView)]", key);
        self.scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(verticalConstraintsFormat1 as String, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: subViews as AnyObject as! [String : AnyObject]));


    }
    self.scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat(verticalConstraintsFormat as String, options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: subViews as AnyObject as! [String : AnyObject]));

Hope this helps.

Sunny Shah
  • 12,990
  • 9
  • 50
  • 86
1

In our project we had encountered similar issue where we had to add a lot of views to a content view of a scrollview using constraints added programatically. Writing constraints for each view not only made the view controller bloated, it was also static. To add another view we had to write the constraints again.

We end up creating subclass of UIView that now managed this for us. We named this NNVerticalStackView.h,.m.

The gist of this class is the below method.

-(void)addView:(UIView *)view
     belowView:(UIView *)viewAbove
     aboveView:(UIView *)viewBelow
    withHeight:(CGFloat)height{

    view.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:view];

    if(height != NNStackViewContentHeightMetric && height > 0) {
        NSArray * heightConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[view(height)]" options:0 metrics:@{@"height":@(height)} views:NSDictionaryOfVariableBindings(view)];
        [view addConstraint:heightConstraint.firstObject];
    }

    NSArray * defaultConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-0-[view]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)];
    NSLayoutConstraint * upperConstraint,* lowerConstraint;

    if(viewAbove==self) {
        [self removeConstraint:_topConstraint];
        upperConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)].firstObject;
        _topConstraint = upperConstraint;
    }
    else
        upperConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[viewAbove]-0-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view,viewAbove)].firstObject;

    if(viewBelow==self) {
        [self removeConstraint:_bottomConstraint];
        lowerConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[view]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view)].firstObject;
        _bottomConstraint = lowerConstraint;
    }
    else
        lowerConstraint = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[view]-0-[viewBelow]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(view,viewBelow)].firstObject;

    [self addConstraints:defaultConstraints];
    [self addConstraints:@[upperConstraint,lowerConstraint]];
}

For the top most view, viewAbove is the container view (i.e. self), other wise it represents the at view above the view being added. For the bottom most view, viewBelow is the container view other wise its the view below the current view being added.

So with this subclass what you intend to do can be easily coded as.

let N = 4
let holder = NNVerticalStackView(frame: CGRectMake(0, 0, someWidth, CGFloat(N) * 100.0))
for _ in 0...N {
    let content = UIView(frame: CGRect.zero) //height should be 100
    holder.insertStackItem(content, atIndex: 0, withItemHeight: 100.0)
}

For offsets, you might need to change the custom class to define your own offsets.

BangOperator
  • 4,377
  • 2
  • 24
  • 38