How to use AutoLayout to position UIButtons in horizontal lines (wrapping, left aligned)?

后端 未结 3 1932
爱一瞬间的悲伤
爱一瞬间的悲伤 2020-12-25 14:40

I need to create a couple of UIButtons with various widths programmatically in my app (iOS 6.0 and above).

I want to display the buttons in a \"wrap around\" style:

相关标签:
3条回答
  • 2020-12-25 15:17

    Here is the another example of how we can implement wrapping layout with auto layout:

        @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
    

    Here I'm using PureLayout for defining constrains.

    You can use this class like this:

    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];
    
    0 讨论(0)
  • 2020-12-25 15:23

    My current solution looks like this: No AutoLayout, but manually setting the correct constraints for each case (first button, leftmost button in a new line, any other button).

    (My guess is that setting the frame for each button directly would result in more readable code than using NSLayoutConstraints, anyway)

    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];
    }
    
    0 讨论(0)
  • 2020-12-25 15:37

    Instead of using Autolayout, you could just use a collection view which better options for you to lay out elements such as buttons.

    It is better able to handle layouts under rotation as well.

    0 讨论(0)
提交回复
热议问题