Как использовать Autolayout для установки UIButtons в горизонтальные линии (упаковка, выравнивание по левому краю)?
Мне нужно создать пару UIButtons с различной шириной программно в моем приложении (iOS 6.0 и выше).
Я хочу отобразить кнопки в стиле "обертывания": начиная с левого края, каждая кнопка должна быть расположена рядом друг с другом горизонтально (в определенном порядке), и если кнопка не помещается в текущую "линию", она должна начать новую линию на левом краю ниже предыдущей строки.
Примечание: Я не хочу таблицу / сетку, так как кнопки имеют разные ширины, и я хочу иметь одну прямо рядом друг с другом.
Я мог бы вручную вычислить фрейм каждой кнопки в моем коде, но должен ли я использовать AutoLayout (с программно созданными NSLayoutConstraints) вместо этого? Как именно мне нужно будет его настроить?
EDIT: после прочтения главы 4 "промежуточная автоматическая компоновка" из "iOS 6 by Tutorials" я не уверен, может ли использование чистого AutoLayout реализовать эту функцию "обертывания". требовать.
3 ответа:
Мое текущее решение выглядит следующим образом: нет автозапуска, но вручную устанавливаем правильные ограничения для каждого случая (первая кнопка, крайняя левая кнопка в новой строке, любая другая кнопка).
(я предполагаю, что установка фрейма для каждой кнопки напрямую приведет к более удобочитаемому коду, чем использование 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]; }
Вместо использования Autolayout, вы можете просто использовать представление коллекции, которое лучше подходит для размещения элементов, таких как кнопки.
Он также лучше справляется с макетами при вращении.
Вот еще один пример того, как мы можем реализовать компоновку обтекания с автоматической компоновкой:
@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];