Floating autolayout iOS/OSX

I have a need for floating autolayout. I find that its quite hard problem to solve but i think it can be done by using some of the tips described here: http://www.objc.io/issue-3/advanced-auto-layout-toolbox.html

Maybe someone have already tried solving such problem or would like to try it.

  • Pass data between view controller segue
  • In-place editing of UITableView cell
  • Prevent app from backing up documents folder?
  • Updating sqlite database without XML
  • How to display an array in reverse order in objective C
  • is it possible to know if user is typing or deleting characters in a textfield?
  • So here is the challenge:
    enter image description here

    nicely floated views which drops to new line if content width exceeded, like in next picture view1 width is bigger so view2 drops to new line. (same would happen if view2 width would become bigger)

    enter image description here

    All views come in sequence, views can have defined min width and max width should be the container width. views can stretch in height but then they always take full content width.

    2 Solutions Collect From Internet About “Floating autolayout iOS/OSX”

    I worked on this challenge tonight. I ran the code in the iPhone Simulator; it seems to work. However, I did not attempt to match the exact specifications of the OP, nor did I follow the link to tips on how to do this. I just wanted to see what I could knock out on my own in a couple of hours.

    There’s nothing to see in storyboard except an empty, yellow scroll view pinned to the sides of the root view.

    The gray floating views are inside a yellow scroll view. The width of the scroll view’s content size is the width of the root view; the height of the content size shrinks and expands to accommodate the varying number of rows.

    The only place I didn’t use Auto Layout was for the scroll view’s content view (here, I used Apple’s so-called “Mixed Approach”).

    The widths of the floating cells are randomly generated whenever viewWillLayoutSubviews is called. Hence, all floating cells change their width upon device rotation. I held the height of all floating cells to a constant.

    enter image description here

    enter image description here

    @interface ViewController ()
    
    @property (nonatomic, strong) NSMutableArray *floatingViews;
    @property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
    @property (nonatomic, strong) UIView *contentView;
    
    @end
    
    @implementation ViewController
    
    #define NUM_OF_VIEWS 18
    #define HEIGHT 30.0f
    #define HORIZONTAL_SPACER 20.0f
    #define VERTICAL_SPACER 10.0f
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        self.contentView = [[UIView alloc] initWithFrame:self.view.bounds];
        [self.scrollView addSubview:self.contentView];
    
        self.floatingViews = [[NSMutableArray alloc] init];
    
        for (int i = 0; i < NUM_OF_VIEWS; i++) {
            UIView *view = [[UIView alloc] init];
            view.backgroundColor = [UIColor grayColor];
            view.translatesAutoresizingMaskIntoConstraints = NO;
            [self.floatingViews addObject:view];
            [self.contentView addSubview:view];
        }
    }
    
    - (void)viewWillLayoutSubviews
    {
        [self configureSizeConstraintsForAllViews];
    
        CGFloat superviewWidth = self.view.bounds.size.width;
    
        int row = 0;
        CGFloat leftMargin = 0.0f;
        for (int i = 0; i < [self.floatingViews count]; i++) {
    
            UIView *currentView = self.floatingViews[i];
    
            // is there room for the current view on this row?
            NSLayoutConstraint *widthConstaint = [self widthConstraintForView:currentView];
            CGFloat currentViewWidth = widthConstaint.constant;
    
            if ((leftMargin + currentViewWidth) > superviewWidth) {
                row++;
                leftMargin = 0.0f;
            }
    
            // position current view
            [self configureTopConstraintForView:currentView forRow:row];
            [self configureLeftConstraintForView:currentView withConstant:leftMargin];
    
            // update leftMargin
            leftMargin += currentViewWidth + HORIZONTAL_SPACER;
        }
    
        // update size of content view and scroll view's content size
        CGRect rect = self.contentView.frame;
        rect.size.width = superviewWidth;
        rect.size.height = row * (HEIGHT + VERTICAL_SPACER) + HEIGHT;
        self.contentView.frame = rect;
        [self.scrollView setContentSize:rect.size];
    }
    
    - (void)configureSizeConstraintsForAllViews
    {
        static BOOL firstTime = YES;
        if (firstTime) {
            firstTime = NO;
            [self configureHeightConstraintsForAllViews];
        }
    
        for (int i = 0; i < [self.floatingViews count]; i++) {
            [self configureRandomWidthForView:self.floatingViews[i]];
        }
    }
    
    - (void)configureRandomWidthForView:(UIView *)view
    {
        CGFloat maxWidth = self.view.bounds.size.width;
        CGFloat minWidth = 30.0f;
    
        CGFloat randomScale = (arc4random() % 101) / 100.0f; // 0.0 - 1.0
    
        CGFloat randomWidth = minWidth + randomScale * (maxWidth - minWidth);
    
        assert(randomWidth >= minWidth && randomWidth <= maxWidth);
    
        NSLayoutConstraint *widthConstraint = [self widthConstraintForView:view];
    
        if (!widthConstraint) {
            widthConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:0.0f];
            [view addConstraint:widthConstraint];
        }
    
        widthConstraint.constant = randomWidth;
    }
    
    - (NSLayoutConstraint *)widthConstraintForView:(UIView *)view
    {
        NSLayoutConstraint *widthConstraint = nil;
    
        for (NSLayoutConstraint *constraint in view.constraints) {
            if (constraint.firstAttribute == NSLayoutAttributeWidth) {
                widthConstraint = constraint;
                break;
            }
        }
    
        return widthConstraint;
    }
    
    - (NSLayoutConstraint *)topConstraintForView:(UIView *)view
    {
        NSLayoutConstraint *topConstraint = nil;
    
        for (NSLayoutConstraint *constraint in view.superview.constraints) {
            if (constraint.firstItem == view || constraint.secondItem == view) {
                if (constraint.firstAttribute == NSLayoutAttributeTop) {
                    topConstraint = constraint;
                    break;
                }
            }
        }
    
        return topConstraint;
    }
    
    - (NSLayoutConstraint *)leftConstraintForView:(UIView *)view
    {
        NSLayoutConstraint *leftConstraint = nil;
    
        for (NSLayoutConstraint *constraint in view.superview.constraints) {
            if (constraint.firstItem == view || constraint.secondItem == view) {
                if (constraint.firstAttribute == NSLayoutAttributeLeft) {
                    leftConstraint = constraint;
                    break;
                }
            }
        }
    
        return leftConstraint;
    }
    
    - (void)configureHeightConstraintsForAllViews
    {
        assert(self.floatingViews);
    
        for (int i = 0; i < [self.floatingViews count]; i++) {
            UIView *view = self.floatingViews[i];
            [view addConstraint:[NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0f constant:HEIGHT]];
        }
    }
    
    - (void)configureTopConstraintForView:(UIView *)view forRow:(NSUInteger)row
    {
        NSLayoutConstraint *topConstraint = [self topConstraintForView:view];
        if (!topConstraint) {
            topConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f];
            [view.superview addConstraint:topConstraint];
        }
    
        topConstraint.constant = row * (HEIGHT + VERTICAL_SPACER);
    }
    
    - (void)configureLeftConstraintForView:(UIView *)view withConstant:(CGFloat)constant
    {
        NSLayoutConstraint *leftConstraint = [self leftConstraintForView:view];
        if (!leftConstraint) {
            leftConstraint = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:view.superview attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f];
            [view.superview addConstraint:leftConstraint];
        }
    
        leftConstraint.constant = constant;
    }
    
    - (BOOL)prefersStatusBarHidden
    {
        return YES;
    }
    
    @end
    

    To simplify, take inspiration from the way Covoa text system works. Then a little from Core Text and auto layout.

    Each of those views would go into an array.
    Each of them would have no content compression and lots of content hugging.
    Each line would be an NSStackView with horizontal orientation.
    All of those would go in an NSStackView with vertical orientation.
    That into a scroll view.

    If a view doesn’t fit in stack view line 1 you readjust down each stack view line.

    It would work.

    It might be slow if this gets really big.