如何垂直自动布局 UIViews 列表,一个在彼此之上?

Posted

技术标签:

【中文标题】如何垂直自动布局 UIViews 列表,一个在彼此之上?【英文标题】:How do I auto-layout a list of UIViews vertically, one on top of each other? 【发布时间】:2016-05-30 04:59:50 【问题描述】:
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)

上面的代码会将N个UIViews添加到holder。我想以编程方式使用NSLayoutConstraints 垂直显示内容,一个在一个之上。

每个内容的高度应为 100。每个内容应在所有 4 条边上都有约束。第一个内容应该对holder 有顶部/前导/尾随约束(常量=0)。最后一个内容应该有底部/前导/尾随约束(常量=0)到holder

其余内容应该对其他同级内容具有顶部/底部约束 (constant=8)。

如何创建这些约束?

【问题讨论】:

有什么理由你更喜欢手动布局而不是NSStackView 【参考方案1】:

我为水平页面所做的相同。所以这里是代码

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

添加holderview的子视图

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

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

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

制作水平字符串约束

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

创建动态页面

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

希望这会有所帮助。

【讨论】:

【参考方案2】:

在我们的项目中,我们遇到了类似的问题,我们必须使用以编程方式添加的约束将大量视图添加到滚动视图的内容视图中。为每个视图编写约束不仅使视图控制器臃肿,而且是静态的。要添加另一个视图,我们必须再次编写约束。

我们最终创建了现在为我们管理这个的 UIView 的子类。我们将其命名为NNVerticalStackView.h,.m。

这个类的要点是下面的方法。

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

对于最顶层的视图,viewAbove 是容器视图(即self),否则它代表正在添加的视图上方的 at 视图。 对于最底部的视图,viewBelow 是容器视图,否则它是正在添加的当前视图下方的视图。

所以有了这个子类,你打算做的事情可以很容易地编码。

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)

对于偏移量,您可能需要更改自定义类来定义自己的偏移量。

【讨论】:

【参考方案3】:

你可以这样做,

   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)

这里你应该考虑holder而不是view

此约束适用于中间视图。对于第一个和最后一个视图场景应该是不同的,例如,

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

您可以通过更改约束的constant 来管理空间或距离。如果您希望视图之间的垂直间距为 20 像素,那么您的常数为 top constraints should be 20

您可以参考this answer作为参考。

不要同时给出高度和底部。如果你给定高度,那么你不需要底部,如果你给底部,那么你不需要固定高度。

希望这会有所帮助:)

【讨论】:

以上是关于如何垂直自动布局 UIViews 列表,一个在彼此之上?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用自动布局“项目格式约束”为 UIViews 设置相等的宽度

UIViews 的高度与屏幕的高度成比例(自动布局)

是否可以定义相对于未知 UIViews 的自动布局约束?

iOS:使用自动布局保持视图彼此按比例调整大小

使用自动布局对齐两个 uiviews

遇到 NSLayoutConstraints 问题并以编程方式使用自动布局时,UIViews 停留在 (0,0)