如何以编程方式使约束(自动布局)工作

时间:2015-04-15 22:24:20

标签: ios objective-c xcode constraints

这是我的第一个iOS项目,因此我遇到了约束和自动布局的困难。

这是我想要做的。我想将子视图添加到UIScrollView。我这是以编程方式进行的,因为子视图的数量不是静态的。当我只是添加单个子视图(在代码或XIB中)或通过XIB添加多个子视图时,我可以使自动布局和约束完美地工作。但我无法在代码中使用动态数量的子视图。

创建了子视图的众多实例,但由于约束不起作用,它们都是彼此重叠的。我希望他们在每个下面排队,这就是为什么我试图让约束工作。 (现在我只是将子视图的数量设置为10,但这只是暂时的)

以下是我的一些代码(从按下按钮开始):

-(IBAction) buttonTapped:(id)sender {

UIScrollView *matchScrollView = (UIScrollView *) [self.view viewWithTag:1];
NSMutableArray *matchViewsArray = [[NSMutableArray alloc] init];

for (int i = 0; i <= 9; i++) {
    MatchView *newMatchView = [[NSBundle mainBundle] loadNibNamed:@"MatchView" owner:self options:nil].firstObject;
    [matchViewsArray addObject:newMatchView];
}

for (int i = 0; i < [matchViewsArray count]; i++) {
    MatchView *newMatchView = matchViewsArray[i];

//I kept some IBOutlets out of the code example here. They work fine with a single subview. or even multiple subviews as long as you don't mind they are all on top of each other due to constraints not working xD
    [newMatchView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [matchScrollView addSubview:matchViewsArray[i]];

    [matchScrollView addConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i]
                                                              attribute:NSLayoutAttributeLeading
                                                              relatedBy:NSLayoutRelationEqual
                                                                 toItem:matchScrollView
                                                              attribute:NSLayoutAttributeLeading
                                                             multiplier:1.0
                                                               constant:8.0]];

    [matchScrollView addConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i]
                                                                attribute:NSLayoutAttributeTrailing
                                                                relatedBy:NSLayoutRelationEqual
                                                                   toItem:matchScrollView
                                                                attribute:NSLayoutAttributeTrailing
                                                               multiplier:1.0
                                                                 constant:8.0]];

    //If this is not the first object, then the top of the object contraints with the bottom of the previous object
    if (i > 0) {
        // I think the fault may lie here. Do i need to remove the previous constraint, or will it be overwritten? This did not work however, did i remove it wrong?

        //Making sure it only tries to call removeconstraint: once and once only, as the constraint is only put on the else statement which only executes on the very first object. Attempt to fix the code
        if (i == 1) {
        [matchScrollView removeConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i - 1]
                                                                    attribute:NSLayoutAttributeTop
                                                                    relatedBy:NSLayoutRelationEqual
                                                                        toItem:matchScrollView
                                                                    attribute:NSLayoutAttributeTop
                                                                    multiplier:1.0
                                                                    constant:8.0]];
        }

        [matchViewsArray[i - 1] addConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i]
                                                                    attribute:NSLayoutAttributeTop
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:matchViewsArray[i - 1]
                                                                    attribute:NSLayoutAttributeBottom
                                                                   multiplier:1.0
                                                                     constant:8.0]];
    }

    //The top of the first object constraints with the top of the matchScrollView
    else {
        [matchScrollView addConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i]
                                                                      attribute:NSLayoutAttributeTop
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:matchScrollView
                                                                      attribute:NSLayoutAttributeTop
                                                                     multiplier:1.0
                                                                       constant:8.0]];
    }

    //If this is the last object, then the bottom of the object constraints with the bottom of matchScrollView
    if ( i == [matchViewsArray count] - 1) {
        //tried removing the constraint created by else statement first before adding a new constraint
        [matchViewsArray[i] removeConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i - 1]
                                                                           attribute:NSLayoutAttributeBottom
                                                                           relatedBy:NSLayoutRelationEqual
                                                                              toItem:matchViewsArray[i]
                                                                           attribute:NSLayoutAttributeTop
                                                                          multiplier:1.0
                                                                            constant:8.0]];

        [matchScrollView addConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i]
                                                                    attribute:NSLayoutAttributeBottom
                                                                    relatedBy:NSLayoutRelationGreaterThanOrEqual
                                                                       toItem:matchScrollView
                                                                    attribute:NSLayoutAttributeBottom
                                                                   multiplier:1.0
                                                                     constant:8.0]];
    }
    //else the bottom of the object contraints with the top of the next object
    else {
        [matchViewsArray[i + 1] addConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i]
                                                                    attribute:NSLayoutAttributeBottom
                                                                    relatedBy:NSLayoutRelationEqual
                                                                       toItem:matchViewsArray[i + 1]
                                                                    attribute:NSLayoutAttributeTop
                                                                   multiplier:1.0
                                                                     constant:8.0]];
    }

    [matchScrollView addConstraint:[NSLayoutConstraint constraintWithItem:matchViewsArray[i]
                                                                attribute:NSLayoutAttributeCenterX
                                                                relatedBy:NSLayoutRelationEqual
                                                                   toItem:matchScrollView
                                                                attribute:NSLayoutAttributeCenterX
                                                               multiplier:1.0
                                                                 constant:0.0]];
}

}

我一直在“以NSException类型的未捕获异常终止”。我尝试在添加新约束之前删除旧约束,这没有帮助。除非我删除它们错了?

如果我删除所有if和else语句,只创建一个Subview,那么约束就可以完美地运行。如果我创建多个子视图,那么应用程序不会崩溃,但子视图都具有相同的约束并且彼此重叠。因此,通过添加if和else语句并使用matchViewsArray [i - 1],我尝试为多个子视图设置约束。

此外,即使这段代码有效,是否真的没有更简单的方法来代替循环中的所有if和else语句?

如上所述,这是我的第一个iOS项目,因此可能有更有效,更好的方法来实现我想要的行为。

任何帮助都将不胜感激。

1 个答案:

答案 0 :(得分:2)

不幸的是,我没有制作iOS应用程序的许可证,也无法按原样进行测试,但是这个OSX代码段可能有所帮助:

- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
    NSView *contentView = [window contentView];

    NSMutableArray *buttons = [[NSMutableArray alloc] init];

    for (int i = 0; i < 10; i++) {
        NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
        [button setTranslatesAutoresizingMaskIntoConstraints:NO];
        [button setTitle:[NSString stringWithFormat:@"Button %d", (int)pow(10, i)]];
        [button setBezelStyle:NSRoundedBezelStyle];
        [button setFont:[NSFont systemFontOfSize:[NSFont systemFontSize]]];
        [button sizeToFit];

        [buttons addObject:button];
        [contentView addSubview:button];
    }
    int count = (int)[buttons count];

    // 1. one below other
    // 2. all equal width

    for (int i = 1; i < count; i++) {
        NSView *view1 = buttons[i-1];
        NSView *view2 = buttons[i];
        NSDictionary *views = NSDictionaryOfVariableBindings(view1, view2);

        [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[view1]-[view2]"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];
        [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[view1(==view2)]"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];
    }

    // 3. first -> top

    if (count > 0) {
        NSView *view = [buttons firstObject];
        NSDictionary *views = NSDictionaryOfVariableBindings(view);

        [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[view]"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];
    }

    // 4. last -> bottom

    if (count > 0) {
        NSView *view = [buttons lastObject];
        NSDictionary *views = NSDictionaryOfVariableBindings(view);

        [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[view]-|"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];
    }

    // 5. left <- all -> right

    for (int i = 0; i < count; i++) {
        NSView *view = buttons[i];
        NSDictionary *views = NSDictionaryOfVariableBindings(view);

        [contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[view]-(>=20)-|"
                                                                            options:0
                                                                            metrics:nil
                                                                              views:views]];
    }
}

What it looks like

请注意,如果不使用“可视格式语言”,则无法指定默认的8/20间距,因为constraintsWithVisualFormat:...会产生特殊约束(嗯,内部有特殊的私有标记),在特定情况下会自动调整大小8 6/4 /无论如何相应Apple的HIG。 (您可以尝试在IB中放置控件并查看默认值不总是8,但在运行时突然显示!)。

编辑另外,您可能总是使用简单的#define或子程序来隐藏[[[content-constraint-with-contraint-visual-constraint-with-constraints-ponies]]]梯子。

edit2:重构阶段:)