NSLayoutConstraint 用于代码中未知数量的子视图

Posted

技术标签:

【中文标题】NSLayoutConstraint 用于代码中未知数量的子视图【英文标题】:NSLayoutConstraint for unknown number of subviews in code 【发布时间】:2016-07-15 14:38:41 【问题描述】:

我正在进入 ios 编程领域,并且或多或少地掌握了具有固定数量项目的自动布局。比如说,我们有一个 UILabel 代表标题,UILabel 代表副标题,那么约束的视觉格式是'V:|-[title]-10-[subtitle]-|'

但是如果我根据一些 API 响应动态创建子视图会怎样。例如,我需要添加 40 个子视图。我用视觉格式指定每个子视图并跟踪它们已经不现实了。正确的方法是什么?

我想,在我添加每个子视图之后,我会根据前一个视图使用constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: 设置约束。这是要走的路,还是有更好的方法?

【问题讨论】:

【参考方案1】:

我用视觉格式指定每个子视图并跟踪它们已经不现实了

为什么不呢?您似乎认为这是某种“非此即彼”的情况。没有什么能阻止您使用可视化格式来构建约束集合。没有法律规定您必须将所有您的约束放入一个视觉格式。

而且布局引擎不知道也不关心你用什么表示法来形成你的约束。约束就是约束!

考虑一下我在其中为滚动视图和其中的 30 个标签构建约束的代码:

    var con = [NSLayoutConstraint]()
    con.appendContentsOf(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "H:|[sv]|",
            options:[], metrics:nil,
            views:["sv":sv]))
    con.appendContentsOf(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "V:|[sv]|",
            options:[], metrics:nil,
            views:["sv":sv]))
    var previousLab : UILabel? = nil
    for i in 0 ..< 30 
        let lab = UILabel()
        // lab.backgroundColor = UIColor.redColor()
        lab.translatesAutoresizingMaskIntoConstraints = false
        lab.text = "This is label \(i+1)"
        sv.addSubview(lab)
        con.appendContentsOf(
            NSLayoutConstraint.constraintsWithVisualFormat(
                "H:|-(10)-[lab]",
                options:[], metrics:nil,
                views:["lab":lab]))
        if previousLab == nil  // first one, pin to top
            con.appendContentsOf(
                NSLayoutConstraint.constraintsWithVisualFormat(
                    "V:|-(10)-[lab]",
                    options:[], metrics:nil,
                    views:["lab":lab]))
         else  // all others, pin to previous
            con.appendContentsOf(
                NSLayoutConstraint.constraintsWithVisualFormat(
                    "V:[prev]-(10)-[lab]",
                    options:[], metrics:nil,
                    views:["lab":lab, "prev":previousLab!]))
        
        previousLab = lab
    
    con.appendContentsOf(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "V:[lab]-(10)-|",
            options:[], metrics:nil,
            views:["lab":previousLab!]))
    NSLayoutConstraint.activateConstraints(con)

我正在使用可视化格式,但我在添加视图时一次执行一个约束(这正是您要问的)。

【讨论】:

哈。现在我觉得有点愚蠢:) 谢谢 请注意,如果您希望滚动视图将其contentSize.height 设置为实际允许滚动,您还需要将最后一个标签限制在滚动视图的底部。 @robmayoff 做到了,只是在粘贴时被切断了【参考方案2】:

答案真的取决于你想要什么样的布局,因为有更高级的工具可以制作一些布局。

从 iOS 9 开始,您可以使用UIStackView 来管理一行或一列视图。 By nesting stack views, you can easily make a grid of views. 要了解UIStackView,请先观看“Mysteries of Auto Layout, Part 1” from WWDC 2015。如果您在代码中完成所有布局,则可以使用TZStackView,这是UIStackView 的重新实现,适用于 iOS 7 及更高版本。

UICollectionView 是另一种用于在网格或许多其他排列中布置视图的工具。它比UIStackView 更复杂,但是有很多介绍性材料可以教你如何使用它,例如“Introducing Collection Views” from WWDC 2012。

【讨论】:

【参考方案3】:

我构建了一个动态布局引擎(水平布局和垂直布局)。

    它接受一组视图。

    它将视图作为子视图添加到容器中。

    然后动态组成一个垂直和水平的视觉布局字符串。 (它为使用 v%p 布局的每个视图生成一个唯一的“对象地址”字符串。 只需在 viewDidLoad 中调用:[myView addControls:@[view1, view2, view3]],它就会为您垂直布局。

    这是一个用于垂直布局的通用布局引擎。享受吧。

    @implementation UIView (VerticalLayout)
    
    - (void)addControls:(NSArray*)controls
        align:(VerticalAlignment)alignment
        withHeight:(CGFloat)height
        verticalPadding:(CGFloat)verticalPadding
        horizontalPadding:(CGFloat)horizontalPadding
        topPadding:(CGFloat)topPadding
        bottomPadding:(CGFloat)bottomPadding
        withWidth:(CGFloat)width
    
        NSMutableDictionary* bindings = [[NSMutableDictionary alloc] init];
        NSDictionary *metrics = @
            @"topPadding": @(topPadding),
            @"bottomPadding": @(bottomPadding),
            @"verticalPadding": @(verticalPadding),
            @"horizontalPadding":@(horizontalPadding) ;
    
        NSMutableString* verticalConstraint = [[NSMutableString alloc] initWithString:@"V:"];
        if (alignment == VerticalAlignTop || alignment == VerticalAlignStretchToFullHeight) 
            [verticalConstraint appendString:@"|"];
        
    
        for (UIView* view in controls) 
            BOOL isFirstView = [controls firstObject] == view;
            BOOL isLastView = [controls lastObject] == view;
    
            [self addSubview: view];
    
            // Add to vertical constraint string
            NSString* viewName = [NSString stringWithFormat:@"v%p",view];
            [bindings setObject:view forKey:viewName];
    
            NSString* yLeadingPaddingString = @"";
            NSString* yTrailingPadding = @"";
            if (isFirstView && topPadding > 0) 
                yLeadingPaddingString = @"-topPadding-";
            
            else if (!isFirstView && verticalPadding > 0) 
                yLeadingPaddingString = @"-verticalPadding-";
            
    
            if (isLastView && bottomPadding > 0) 
                yTrailingPadding = @"-bottomPadding-";
            
    
            [verticalConstraint appendFormat:@"%@[%@%@]%@",
                yLeadingPaddingString,
                viewName,
                height > 0 ? [NSString stringWithFormat:@"(%f)", height] : @"",
                yTrailingPadding];
    
            NSArray* constraints = [NSLayoutConstraint constraintsWithVisualFormat:
                    [NSString stringWithFormat: @"H:|-horizontalPadding-[%@%@]%@|",
                    viewName,
                    (width > 0 ? [NSString stringWithFormat:@"(%lf)", width] : @""),
                    width > 0 ? @"-" : @"-horizontalPadding-"]
                options:NSLayoutFormatDirectionLeadingToTrailing
                metrics:metrics
                views:bindings];
    
            // high priority, but not required.
            for (NSLayoutConstraint* constraint in constraints) 
                constraint.priority = 900;
            
    
            // Add the horizontal constraint
            [self addConstraints: constraints];
        
    
        if (alignment == VerticalAlignBottom || alignment == VerticalAlignStretchToFullHeight) 
            [verticalConstraint appendString:@"|"];
        
    
        NSArray* constraints = [NSLayoutConstraint
                constraintsWithVisualFormat:verticalConstraint
                options:NSLayoutFormatAlignAllLeading
                metrics:metrics
                views:bindings];
        for (NSLayoutConstraint* constraint in constraints) 
            constraint.priority = 900;
        
    
        // Add the vertical constraints for all these views.
        [self addConstraints:constraints];
    
    

【讨论】:

以上是关于NSLayoutConstraint 用于代码中未知数量的子视图的主要内容,如果未能解决你的问题,请参考以下文章

获取 NSLayoutConstraint 标识符不适用于 topAnchor

@IBDesignable , NSLayoutConstraint 用于超级视图的比例乘数宽度?

NSLayoutConstraint 用于 UITableView 上的视图

在 iOS7 中设置 NSLayoutConstraint 常量

NSLayoutConstraint 和 safeAreaLayoutGuide - 编码兼容 pre-iOS 11

iOS开发NSLayoutConstraint代码自动布局