【问题标题】:How to use AutoLayout to position UIButtons in horizontal lines (wrapping, left aligned)?如何使用 AutoLayout 将 UIButtons 定位在水平线中(环绕,左对齐)?
【发布时间】:2013-11-17 11:49:37
【问题描述】:

我需要在我的应用程序(iOS 6.0 及更高版本)中以编程方式创建几个不同宽度的 UIButton。

我想以“环绕”样式显示按钮:从左边缘开始,每个按钮应水平放置(按定义的顺序),如果按钮不适合当前"line",它应该在前一行下方的左边缘开始新的一行。

注意:我不想要表格/网格,因为按钮有不同的宽度,我希望有一个彼此相邻。

我可以在我的代码中手动计算每个按钮的框架,但我应该使用 AutoLayout(使用以编程方式创建的 NSLayoutConstraints)来代替吗?我究竟需要如何设置它?

编辑:阅读"iOS 6 by Tutorials" 的第 4 章“中间自动布局”后,我不确定使用纯 AutoLayout 是否可以实现我需要的这种“环绕”功能。

【问题讨论】:

  • 我建议你阅读这篇文章。 raywenderlich.com/20881
  • @Igor 这篇文章根本没有提到以编程方式创建的 NSLayout 约束! (但它提醒我,我拥有“iOS 6 By Tutorials”,其中有一章“中级自动布局”,应该涵盖基础知识 - 所以还是谢谢 ;-))

标签: ios xcode interface-builder autolayout nslayoutconstraint


【解决方案1】:

我当前的解决方案如下所示:没有 AutoLayout,但为每种情况手动设置正确的约束(第一个按钮、新行中最左边的按钮、任何其他按钮)。

(我的猜测是直接为每个按钮设置框架会比使用 NSLayoutConstraints 产生更可读的代码,无论如何)

NSArray *texts = @[ @"A", @"Short", @"Button", @"Longer Button", @"Very Long Button", @"Short", @"More Button", @"Any Key"];

int indexOfLeftmostButtonOnCurrentLine = 0;
NSMutableArray *buttons = [[NSMutableArray alloc] init];
float runningWidth = 0.0f;
float maxWidth = 300.0f;
float horizontalSpaceBetweenButtons = 10.0f;
float verticalSpaceBetweenButtons = 10.0f;

for (int i=0; i<texts.count; i++) {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [button setTitle:[texts objectAtIndex:i] forState:UIControlStateNormal];
    [button sizeToFit];
    button.translatesAutoresizingMaskIntoConstraints = NO;

    [self.view addSubview:button];

    // check if first button or button would exceed maxWidth
    if ((i == 0) || (runningWidth + button.frame.size.width > maxWidth)) {
        // wrap around into next line
        runningWidth = button.frame.size.width;

        if (i== 0) {
            // first button (top left)
            // horizontal position: same as previous leftmost button (on line above)
            NSLayoutConstraint *horizontalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0f constant:horizontalSpaceBetweenButtons];
            [self.view addConstraint:horizontalConstraint];

            // vertical position:
            NSLayoutConstraint *verticalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop              multiplier:1.0f constant:verticalSpaceBetweenButtons];
            [self.view addConstraint:verticalConstraint];


        } else {
            // put it in new line
            UIButton *previousLeftmostButton = [buttons objectAtIndex:indexOfLeftmostButtonOnCurrentLine];

            // horizontal position: same as previous leftmost button (on line above)
            NSLayoutConstraint *horizontalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:previousLeftmostButton attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f];
            [self.view addConstraint:horizontalConstraint];

            // vertical position:
            NSLayoutConstraint *verticalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousLeftmostButton attribute:NSLayoutAttributeBottom multiplier:1.0f constant:verticalSpaceBetweenButtons];
            [self.view addConstraint:verticalConstraint];

            indexOfLeftmostButtonOnCurrentLine = i;
        }
    } else {
        // put it right from previous buttom
        runningWidth += button.frame.size.width + horizontalSpaceBetweenButtons;

        UIButton *previousButton = [buttons objectAtIndex:(i-1)];

        // horizontal position: right from previous button
        NSLayoutConstraint *horizontalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:previousButton attribute:NSLayoutAttributeRight multiplier:1.0f constant:horizontalSpaceBetweenButtons];
        [self.view addConstraint:horizontalConstraint];

        // vertical position same as previous button
        NSLayoutConstraint *verticalConstraint = [NSLayoutConstraint constraintWithItem:button attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:previousButton attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f];
        [self.view addConstraint:verticalConstraint];
    }

    [buttons addObject:button];
}

【讨论】:

  • 当您使用自动布局时,我会避免直接为视图设置框架。
  • 是的,我的意思是直接设置框架,不使用任何自动布局。
  • 谢谢你,但是你在第一个按钮的verticalConstraint 上有错误吗?您不应该将按钮的顶部设置为包含视图的顶部,而不是左侧吗?
  • 感谢 mohrtan,已修复。 (这里的代码不是我在实际应用程序中所做的 100%,所以这个错误似乎来自复制/粘贴。)
【解决方案2】:

这是我们如何使用自动布局实现包装布局的另一个示例:

    @interface SCHorizontalWrapView : UIView
        @property(nonatomic)NSMutableArray *wrapConstrains;
    @end


@implementation SCHorizontalWrapView {
    CGFloat intrinsicHeight;
    BOOL updateConstraintsCalled;
}

-(id)init {
    self = [super init];
    if (self) {
        [UIView autoSetPriority:UILayoutPriorityDefaultHigh forConstraints:^{
            [self autoSetContentCompressionResistancePriorityForAxis:ALAxisVertical];
            [self autoSetContentCompressionResistancePriorityForAxis:ALAxisHorizontal];
            [self autoSetContentCompressionResistancePriorityForAxis:ALAxisHorizontal];
            [self autoSetContentCompressionResistancePriorityForAxis:ALAxisVertical];
        }];
    }
    return self;
}

-(void)updateConstraints {
    if (self.needsUpdateConstraints) {
        if (updateConstraintsCalled == NO) {
            updateConstraintsCalled = YES;
            [self updateWrappingConstrains];
            updateConstraintsCalled = NO;
        }

        [super updateConstraints];
    }
}

-(NSMutableArray *)wrapConstrains {
    if (_wrapConstrains == nil) {
        _wrapConstrains = [NSMutableArray new];

    }
    return _wrapConstrains;
}

-(CGSize)intrinsicContentSize {
    return CGSizeMake(UIViewNoIntrinsicMetric, intrinsicHeight);
}

-(void)setViews:(NSArray*)views {
    if (self.wrapConstrains.count > 0) {
        [UIView autoRemoveConstraints:self.wrapConstrains];
        [self.wrapConstrains removeAllObjects];
    }

    NSArray *subviews = self.subviews;
    for (UIView *view in subviews) {
        [view removeFromSuperview];
    }
    for (UIView *view in views) {
        view.translatesAutoresizingMaskIntoConstraints = NO;
        [self addSubview:view];
        CGFloat leftPadding = 0;
        [view autoSetDimension:ALDimensionWidth toSize:CGRectGetWidth(self.frame) - leftPadding relation:NSLayoutRelationLessThanOrEqual];
    }
}


-(void)updateWrappingConstrains {

    NSArray *subviews = self.subviews;
    UIView *previewsView = nil;
    CGFloat leftOffset = 0;
    CGFloat itemMargin = 5;
    CGFloat topPadding = 0;
    CGFloat itemVerticalMargin = 5;
    CGFloat currentX = leftOffset;
    intrinsicHeight = topPadding;
    int lineIndex = 0;
    for (UIView *view in subviews) {
        CGSize size = view.intrinsicContentSize;
        if (previewsView) {
            [self.wrapConstrains addObject:[view autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topPadding relation:NSLayoutRelationGreaterThanOrEqual]];
            [self.wrapConstrains addObject:[view autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:leftOffset relation:NSLayoutRelationGreaterThanOrEqual]];

            CGFloat width = size.width;
            currentX += itemMargin;
            if (currentX + width <= CGRectGetWidth(self.frame)) {
                [self.wrapConstrains addObject:[view autoConstrainAttribute:ALEdgeLeading toAttribute:ALEdgeTrailing ofView:previewsView withOffset:itemMargin relation:NSLayoutRelationEqual]];
                [self.wrapConstrains addObject:[view autoAlignAxis:ALAxisBaseline toSameAxisOfView:previewsView]];
                currentX += size.width;
            }else {
                [self.wrapConstrains addObject: [view autoConstrainAttribute:ALEdgeTop toAttribute:ALEdgeBottom ofView:previewsView withOffset:itemVerticalMargin relation:NSLayoutRelationGreaterThanOrEqual]];
                currentX = leftOffset + size.width;
                intrinsicHeight += size.height + itemVerticalMargin;
                lineIndex++;
            }

        }else {
            [self.wrapConstrains addObject:[view autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:topPadding relation:NSLayoutRelationEqual]];
            [self.wrapConstrains addObject:[view autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:leftOffset relation:NSLayoutRelationEqual]];
            intrinsicHeight += size.height;
            currentX += size.width;
        }



        [view setNeedsUpdateConstraints];
        [view updateConstraintsIfNeeded];
        [view setNeedsLayout];
        [view layoutIfNeeded];

        previewsView = view;

    }
    [self invalidateIntrinsicContentSize];
}
@end

这里我使用PureLayout 来定义约束。

你可以像这样使用这个类:

SCHorizontalWrapView *wrappingView = [[SCHorizontalWrapView alloc] initForAutoLayout];
//parentView is some view
[parentView addSubview:wrappingView];


[tagsView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:padding];
[tagsView autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:padding];
[tagsView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:locationView withOffset:padding relation:NSLayoutRelationGreaterThanOrEqual];
[tagsView setNeedsLayout];
[tagsView layoutIfNeeded];
[tagsView setNeedsUpdateConstraints];
[tagsView updateConstraintsIfNeeded];
NSMutableArray *views = [NSMutableArray new];
//texts is some array of nsstrings
for (NSString *text in texts) {
    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
    button.translatesAutoresizingMaskIntoConstraints = NO;
    [button setTitle:text forState:UIControlStateNormal];
    button.backgroundColor = [UIColor lightGrayColor];
    [views addObject:button];
}
[tagsView setViews:views];

【讨论】:

    【解决方案3】:

    除了使用自动布局,您还可以使用集合视图,它可以更好地选择布局按钮等元素。

    它也能够更好地处理旋转下的布局。

    【讨论】:

    • 谢谢,我没有想过使用 CollectionView,但我认为用它来实现这种“流/环绕”布局是不可能的,其中集合项(按钮)都留在了-对齐并且到右边缘的距离不同。
    • 您可以根据需要在集合视图中定义布局。如果你愿意,你可以把东西摆成一个圆圈。
    • 我添加了我需要的布局图像。 CollectionView 有可能吗? (我想我必须实现/继承 UICollectionViewLayout - 不确定是否值得为 3-10 个按钮左右的努力)
    • 这不是努力如果它是为了什么。
    • 你知道我需要做什么才能有一个“浮动”的右边缘(所以所有最右边的项目与视图右边缘的距离都不同,就像我的图片一样)?
    猜你喜欢
    • 2014-07-01
    • 2020-09-14
    • 1970-01-01
    • 2015-05-10
    • 1970-01-01
    • 2021-08-13
    • 2014-01-01
    • 2019-03-22
    • 1970-01-01
    相关资源
    最近更新 更多