Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

dantastic

macrumors 6502a
Original poster
Jan 21, 2011
572
678
I do a lot of work on apps where backward compatibility to really old iOS versions is a requirement so I'm only now starting to get up to speed with autolayout. I have been using autolayout for a while now and as long as you use IB and the view isn't too complex it's not too bad. However,

I now have a UIView subclass I'm loading from a nib I need to add to a view. The kicker is I need to add 1-3 instances on this view depending on. I realise I have to create constraints for this in my viewcontroller and attach to each custom view. These are height, width, center horizontally & top edge.

Previously doing the position calculation based on screen size and doing the resizing using strings and struts was only a couple of lines of code. So you could write a very tight loop to draw multiple instances into the screen.

I'm now facing this same problem using autolayout and although I have some working code I'm not happy with the sheer amount of it and was hoping for some help to make this better.

The below is just some test code. In reality the buttons array will hold 1-3 objects which will be used to seed each MyView instance. I want to tastefully put the MyView instances on the top half of the screen.
MyView is a UIView subclass with its own nib file. Init is overridden to return an instance loaded from the nib.

Code:
- (void)viewDidLoad {
    [super viewDidLoad];
   
    NSArray *buttons = @[@{@"title": @"view 1"}, @{@"title": @"view 2"}];
    NSInteger y = ([[UIScreen mainScreen] bounds].size.height / 4.0) - (100 * buttons.count) / 2.0 + 30;
   
    for (NSDictionary *btnDict in buttons) {
       
        MyView *aView = [[MyView alloc] init];
        [aView setButtonInfo:btnDict];
        aView.translatesAutoresizingMaskIntoConstraints = NO;
        [self.view addSubview:aView];
       
        NSString *constraint = [NSString stringWithFormat:@"V:|-%li-[aView]", y];
        y += 110;
       
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[aView(==300)]"    // Set width to 300
                                                                          options:0
                                                                          metrics:nil
                                                                            views:NSDictionaryOfVariableBindings(aView)]];
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[aView(==100)]"    // Set height to 100
                                                                          options:0
                                                                          metrics:nil
                                                                            views:NSDictionaryOfVariableBindings(aView)]];
        [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:constraint            // Place one view under the other
                                                                          options:0
                                                                          metrics:nil
                                                                            views:NSDictionaryOfVariableBindings(aView)]];
        [self.view addConstraint:[NSLayoutConstraint constraintWithItem:aView                            // Center horizontally
                                                              attribute:NSLayoutAttributeCenterX
                                                              relatedBy:NSLayoutRelationEqual
                                                                 toItem:self.view
                                                              attribute:NSLayoutAttributeCenterX
                                                             multiplier:1
                                                               constant:0]];
    }
}

A couple of problems I would like to resolve with the above snippet.
1) Height and width of the view. I would like set this in IB in the nib of MyView, it doesn't feel natural to do this here.
2) In a loop like this, how can I use the previous view as reference to position the next?
3) I find I still have to calculate a starting y position and increment it. This is because problem (2).
4) Centring the view horizontally using visualFormat doesn't seem to work?
5) The above is a truck load of code. I'm lazy! ;)

So what is a better way of achieving this.
 
Some comments

Springs and struts still works. That's what translatesAutoresizingMaskIntoConstraints means. When you addSubview the springs and struts are converted to constraints for you. However, some relationships can be expressed in constraints but not S&S so you need constraints in that case.

There are some convenience methods in iOS 9 that build constraints with a simpler, more compact, API. Look at NSLayoutAnchor.h.

I would attempt to do this by laying out the UI in the storyboard/nib and then removing or hiding the unused buttons. So you build all the relationships in the storyboard. When you removeFromSuperview the related constraints are also removed.

I've done something like this with three buttons that are aligned horizontally. The constraints allow removing any one or two or none of the buttons and the remaining buttons are flush-right in the superview. Each button has a set of constraints to position it relative to its neighbors and another to position it relative to the right edge of the superview. The priorities are set so that the positions relative to its neighbors have a higher priority than the position relative to the superview right edge. So the only code removes the inactive buttons from the superview.
 
Thanks phoney,

So the view MyView is using autolayout internally. It's an absolute case for autolayout as well, springs and struts would be painful! So when you say we can still use springs and struts, is there a combined way of doing it so or? Or how does it work with good old frame calculations?

I hear what you are saying about drawing it out in IB and removing views. And that's fine, that works but it won't scale that well, there will always be a need to be able to loop through a data source and just draw objects.

I haven't looked in to the iOS9 only stuff. Neither have I looked at any of the libraries to make this easier. I'd rather cut my teeth on the real stuff and then use a library when I know what I'm doing.

Looking at the above example. How could it be improved? (For an arbitrary number of views)
 
It now sounds like a tableview or collection view. If you turn off scrolling and the separators it won't really look like a tableview. Just make your myView into a tableviewcell and you can easily have as many as you want. You can easily configure each row. You could also look at UIStackView for this but UITableView sounds simpler.
 
Think snowflakes, Or a horizontal header view. There is always a valid use case to be able to draw out a couple of views.
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.