Arranging UIButtons evenly on 3.5” and 4” display - ios

I have now used a lot of time figuring out - auto layout and how to use it, but haven't found an answer on how to solve this issue.
My issue is that I want to align e.g. 5 vertically placed UIButtons evenly on both iPhone 5 and prior displays, but is there a way to do it with the Interface Builder or is it something which should be done in code. When I want the vertical distance between the buttons to be evenly versus the screen resolution (3.5” and 4” display).
I have tried adding vertical constraints between the buttons, but this only adds a gap in the top or bottom of the layout if 4” display is selected.
I'm of course missing something simple, but can't figure it out.

The only way I've gotten this to work is by putting labels (with no titles, so they're invisible) between the edges and between the buttons, so that the buttons and labels take up all the vertical space on the screen. The buttons have an intrinsic size, but the labels don't, so they expand or contract to fill the space correctly.
-(void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary *viewsDict = [NSMutableDictionary dictionary];
NSArray *titles = #[#"Button 1",#"Button 2",#"Button 3",#"Button 4",#"Button 5"];
for (int i=1; i<6; i++) {
UIButton *b = [UIButton buttonWithType:1];
[b setTitle:titles[i-1] forState:UIControlStateNormal];
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:b forKey:[NSString stringWithFormat:#"b%d",i]];
}
for (int i=1; i<7; i++) { // labels for spacing
UILabel *l = [[UILabel alloc ]init];
[l setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:l forKey:[NSString stringWithFormat:#"l%d",i]];
}
for (id obj in viewsDict.allKeys)
[self.view addSubview:viewsDict[obj]];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[l1][b1][l2(==l1)][b2][l3(==l1)][b3][l4(==l1)][b4][l5(==l1)][b5][l6(==l1)]|"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDict];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"|-100-[b1]"
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDict];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints2];
}
This aligns all the buttons 100 points from the left edge, with equal spacing between the buttons. This will adjust for screen size as well as rotation.

Related

How to create three views using autolayout, with one fix width height and other two growing height

I am trying to create a view setup vertically where i have one UIView (fixed width, height) at the top and two UILabels (fixed width, dynamic height) at the bottom. Padding all around the view (aView) is 5. Padding all around of _mylabel is 5. Padding on left and right of _yourLable is 5. _yourLable will grow as based on text, but when text content is too large, it will just stop to grow for maintain padding from superview of 5.
This is what i have tried:
- (void)viewDidLoad {
[super viewDidLoad];
UIView *superview = self.view;
UIView *aView = [UIView new];
[aView setBackgroundColor:[UIColor blackColor]];
[aView setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.view addSubview:aView];
_mylabel = [[UILabel alloc]init];
[_mylabel setNumberOfLines:0];
[_mylabel setBackgroundColor:[UIColor redColor]];
[_mylabel setTranslatesAutoresizingMaskIntoConstraints:NO];
_mylabel.text = #"i am trying to create a view setup vertically where i have one UIView(fix width, height) at top and other two UILables(fix width, dynamic height) at bottom respectively. Padding on allaround of view/lables is 5. this is what i have tried:";
[self.view addSubview:_mylabel];
_yourLable = [[UILabel alloc]init];
[_yourLable setNumberOfLines:0];
[_yourLable setBackgroundColor:[UIColor redColor]];
[_yourLable setTranslatesAutoresizingMaskIntoConstraints:NO];
_yourLable.text = #"i am trying to create a view setup vertically where i have one UIView(fix width, height) at top and other two UILables(fix width, dynamic height) at bottom respectively. Padding on allaround of view/lables is 5. this is what i have tried:";
[self.view addSubview:_yourLable];
NSDictionary * views = NSDictionaryOfVariableBindings(aView,_mylabel, _yourLable, superview);
NSArray * heightConstraintforLabel = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-5-[aView(==200)]-5-[_mylabel]-5-[_yourLable]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintforView = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[aView]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintformylabel = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[_mylabel]-5-|" options:0 metrics:nil views:views];
NSArray * widthConstraintforyourLable = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-5-[_yourLable]-5-|" options:0 metrics:nil views:views];
[superview addConstraints:heightConstraintforLabel];
[superview addConstraints:widthConstraintforView];
[superview addConstraints:widthConstraintformylabel];
[superview addConstraints:widthConstraintforyourLable];
}
and
-(void)viewWillLayoutSubviews{
[super viewWillLayoutSubviews];
// Your layout logic here
CGFloat availableLabelWidth = _mylabel.frame.size.width;
_mylabel.preferredMaxLayoutWidth = availableLabelWidth;
availableLabelWidth = _yourLable.frame.size.width;
_yourLable.preferredMaxLayoutWidth = availableLabelWidth;
}
This is what i am getting, without warnings:
I want both labels to resize based on exact text height.
I want last red label to grow as per text written in it, but never go beyond bottom space of 5. That is it should grow but maintain bottom padding of 5.
I have tried various combination with vertical content compression for labels..., but not got exact solution.
Help :)
Just in case you have not set the priorities, use
[_mylabel setContentCompressionResistancePriority:751 forAxis:UILayoutConstraintAxisVertical];
[_mylabel setContentHuggingPriority:252 forAxis:UILayoutConstraintAxisVertical];
You don't need to set any priorities for _yourLable.
And you don't need to set the preferredMaxLayoutWidth for any of the labels in viewWillLayoutSubviews, hence you don't need to override viewWillLayoutSubviews. You can comment the whole method.
Verified on iOS 7 and iOS 9 devices.
Simulator screenshot looks like this,

How to programmatically add a UILabel with variable height and layout ios

I have been searching for the whole day, but could not make it work.
I have a UIView with two labels, on above the other, on the left side, and one button on the right side. I added the constraints to let autolayout resize views accordingly. Everything was working perfectly when I had set one constraint for the height of the UIView (and no one constraint for the height of the two UILabel), but as the content of the lower UILabel will vary, I removed that constraint and set two constraints for the UILabel, one fixed for the upper UILabel and one with relation "greater than or equal" for the lower UILabel, and other one to fix the distance between the lower UILabel and the UIView.
It looks like auto layout is not capable of calculate the intrinsicContentSize of the lower UILabel, because it never increases its height above 10px, despite the content of the lower UILabel.
UIView *miView = [[UIView alloc] init];
UILabel *miLabel = [[UILabel alloc] init];
[miLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[miDetailsLabel setNumberOfLines:1];
[miDetailsLabel setText:#"Just one line."];
[miView addSubview:miLabel];
UILabel *miDetailsLabel = [[UILabel alloc] init];
[miDetailsLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[miDetailsLabel setNumberOfLines:0];
[miDetailsLabel setText:#"Enough text to show 3 lines on an IP4, except first of three UILabels on my test code, with no content"];
[miView addSubview:miDetailsLabel];
UIButton *miButton = [[UIButton alloc] init];
[miButton setTranslatesAutoresizingMaskIntoConstraints:NO];
[miView addSubview:miButton];
NSArray *constraint_inner_h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[label]-(10)-[button(52)]-(0)-|" options:0 metrics:nil views:#{#"label":miLabel, #"button":miButton}];
NSArray *constraint_inner2_h = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[details]-(10)-[button]-(0)-|" options:0 metrics:nil views:#{#"details":miDetailsLabel, #"button":miButton}];
NSArray *constraint_label_v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(0)-[label(18)]-(2)-[details(>=10)]-(0)-|" options:0 metrics:nil views:#{#"label":miLabel, #"details":miDetailsLabel}];
NSArray *constraint_button_v = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[button(22)]" options:0 metrics:nil views:#{#"button":miButton}];
[miView addConstraint:[NSLayoutConstraint constraintWithItem:miButton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:miView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[miView addConstraints:constraint_inner_h];
[miView addConstraints:constraint_inner2_h];
[miView addConstraints:constraint_label_v];
[miView addConstraints:constraint_button_v];
I ve put a reduced version of the code.
any ideas of what I am missing?
Thanks
UPDATE : Thanks to Matt advice, I've tried this solution to set the proper value to preferredMawLayoutWidth:
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
NSArray *allKeys = [dictOpcionesTickets allKeys];
for (NSString *key in allKeys) {
NSArray *tmpArray = [dictOpcionesTickets objectForKey:key];
if (![[tmpArray objectAtIndex:0] isEqual:#""]) {
UILabel *tmpLabel = [tmpArray objectAtIndex:3];
tmpLabel.preferredMaxLayoutWidth = tmpLabel.frame.size.width;
NSLog(#"width: %f",tmpLabel.frame.size.width);
}
}
[self.view layoutIfNeeded];
}
To explain the code, as I said, I'm doing it dynamically, so I've created a dictionary with an array with the references to my UILabel (among other interesting information). When I run the code, I get the next log (with the code parsing 3 UILabel, first label with no content):
2014-12-28 20:40:42.898 myapp[5419:60b] width: 0.000000
2014-12-28 20:40:42.912 myapp[5419:60b] width: 0.000000
2014-12-28 20:40:43.078 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.080 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.326 myapp[5419:60b] width: 229.000000
2014-12-28 20:40:43.327 myapp[5419:60b] width: 229.000000
But I'm still getting the same result...the UIView is still showing a height equals to the minimum height set by the constraints, not showing the content of the UILabel.
UPDATE 2 Still fooling around.
I've tested my initial code but simplified on a fresh project in xcode 6, running on an actual iPhone 4 with iOS7, and it worked perfectly without setting preferredMaxLayoutWidth or subclassing UILabel, and even without calling layoutIfNeeded on parent view. But when it comes to the real project (I think it was originally builded in xcode 4 or 5) it does not work:
- (void)addLabelsDinamically {
self.labelFixed.text = #"Below this IB label goes others added dinamically...";
UIButton *miBoton = [[UIButton alloc] init];
miBoton.translatesAutoresizingMaskIntoConstraints = NO;
[miBoton setTitle:#"Hola" forState:UIControlStateNormal];
[self.viewLabels addSubview:miBoton];
[self.viewLabels addConstraint:[NSLayoutConstraint constraintWithItem:miBoton attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.viewLabels attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[boton(22)]" options:0 metrics:nil views:#{#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[boton(40)]" options:0 metrics:nil views:#{#"boton":miBoton}]];
UILabel *miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor redColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(8)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":self.labelFixed}]];
UIView *pre = miLabel;
miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor greenColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbñlkadnlñejtnhalrmvlkamnñnañorenoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(8)-[label]-(10)-[boton]-(8)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(8)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":pre}]];
}
Ah, on the same screen I have one UILabel added on IB, with numberOfLines set to '0', vertical constraint "greater or equal than" and horizontal constraint set in a similar way (but both constraints set on IB)...and it works perfectly...this is driving me nuts!! any help???
Well, finally I've found the error, as I stated in the "UPDATE 2", it was very strange that it was working on an UILabel added via IB, but not with those added dynamically.
So, I looked again to my screen on IB, and found that I have an height constraint with "equal" relation, when I changed it to "greater or equal than" it all started to work!!!! Why I did not notice that the constraint was not "greater or equal than"? Because the view with the constraint was getting larger and larger as I was adding views with labels inside. Is that a bug?
Well, to summarize if someone find this useful. "How to add UILabels multi-line which adapts to its content?"
In iOS 6 versions it was a bug with "preferredMaxLayoutWidth", not getting the width once it was "layouted", and as a result of that programmers must set it via code (the best solution is doing it dynamically, an example of that I put it on my above question).
Fortunately on iOS7 and later, this has been solved, and you can rely only with constraints.
Adding via IB
Add the UILabel, and set "number of lines" to '0' (this will make the UILabel use as many lines as it needs).
Add the constraints, you need at least 3 constraints (pin to top or bottom, and both sides), the fourth will be height with a relation "greater or equal than".
Be sure than the view container has enough room, or also has a height constraint with a relation "greater or equal than".
Adding via code (just paste the code posted above):
UILabel *miLabel = [[UILabel alloc] init]; // Initialitze the UILabel
miLabel.translatesAutoresizingMaskIntoConstraints = NO; // Mandatory to disable old way of positioning
miLabel.backgroundColor = [UIColor redColor];
miLabel.numberOfLines = 0; // Mandatory to allow multiline behaviour
miLabel.text = #"ñkhnaergñsdbmnñgwn"; // big test to test
[self.viewLabels addSubview:miLabel]; // Add subview to the parent view
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:#{#"label":miLabel}]]; // horizontal constraint
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(10)-[label]" options:0 metrics:nil views:#{#"label":miLabel}]];
UIView *pre = miLabel;
// Adding a second label to test the separation
miLabel = [[UILabel alloc] init];
miLabel.translatesAutoresizingMaskIntoConstraints = NO;
miLabel.backgroundColor = [UIColor greenColor];
miLabel.numberOfLines = 0;
miLabel.text = #"ñkhnaermgñlkafmbnoetñnngñsdbmnñgwn";
[self.viewLabels addSubview:miLabel];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[label]-(10)-|" options:0 metrics:nil views:#{#"label":miLabel,#"boton":miBoton}]];
[self.viewLabels addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[previous]-(10)-[label]" options:0 metrics:nil views:#{#"label":miLabel,#"previous":pre}]];
Thanks to those who has tried to help me!! This site is amazing :)
This answer will work in a programmatic situation when you are added the text information dynamically and if this above is on a UICollectionViewCell or UITableViewCell.
In these situations you will need to update the layout and constraints on the contentView, then set the preferedWidth:
-(void) layoutSubviews
{
[super layoutSubviews];
[self.contentView setNeedsLayout];
[self.contentView layoutIfNeeded];
//locationName.preferredMaxLayoutWidth = locationName.frame.size.width;
//locationDetails.preferredMaxLayoutWidth = locationDetails.frame.size.width;
[super layoutSubviews];
}
This is similar to other answers where u are getting the new frame information and using that for the preferredMaxLayoutWidth then updating the view.
You will need constraints added to those views for the vertical as a start point and the UILabel need their numberOfLines = 0
On second thoughts setting he preferredMaxLayoutWidth in the layoutSubViews is poor solution if your width changes, as well as the height. Best to set this else where like the init for that cell. In some ways it might not be needed if you are constraining the width. This in my situation lead to the preferredMaxLayoutWidth changing each time that cell was set.

AutoLayout for a Dynamic number of Views

I have a UIView at the bottom of a UIViewController that can have a different number of views added.
NSUInteger letterCount = [word length];
NSLog(#"The word letter count is: %ld",(unsigned long)letterCount);
for (int i = 0; i < letterCount; i++) {
NSLog(#"new view being created");
UIView *letterTileView = [UIView autolayoutView];
letterTileView.tag = 600 + i;
[self.bottomBarView addSubview:letterTileView];
}
The word could be any word and therefore have a different number of letters. If the word is APPLE it would create 5 subviews in the bottom bar view.
I would like to use Auto Layout to layout these views. Each view should be 48x48 (height x width). I would like these subviews to be centered in the bottom bar view and have padding in between them.
I have used the following method to setup AL before but unsure how to approach the dynamic situation and laying these out correctly.
+ (NSArray *)constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views
Here's an example from my book, where I generate a bunch of UILabels programmatically and add constraints to them as I go along. It's not the same as your situation (my labels are arranged vertically) but it shows how easy it is to add constraints as you are creating interface dynamically:
UILabel* previousLab = nil;
for (int i=0; i<30; i++) {
UILabel* lab = [UILabel new];
// lab.backgroundColor = [UIColor redColor];
lab.translatesAutoresizingMaskIntoConstraints = NO;
lab.text = [NSString stringWithFormat:#"This is label %d", i+1];
[v addSubview:lab];
[v addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(10)-[lab]"
options:0 metrics:nil
views:#{#"lab":lab}]];
if (!previousLab) { // first one, pin to top
[v addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(10)-[lab]"
options:0 metrics:nil
views:#{#"lab":lab}]];
} else { // all others, pin to previous
[v addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[prev]-(10)-[lab]"
options:0 metrics:nil
views:#{#"lab":lab, #"prev":previousLab}]];
}
previousLab = lab;
}
What I've done in the past is create the visual format string dynamically based on the number of views you'll have. Some sample code:
NSArray *views = #[view1, view2, view3]; // this would be a dynamic array with views
NSMutableString *vflString = [NSMutableString string];
NSMutableDictionary *viewsDictionary = [NSMutableDictionary dictionary];
for (int i = 0; i < [views count]; i++) {
NSString *viewIdentifier = [NSString stringWithFormat:#"view%d", i];
[vflString appendString:[NSString stringWithFormat:#"-[%#]-", viewIdentifier];
viewsDictionary[viewIdentifier] = views[i];
}
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:vflString options:0 metrics:nil views:viewsDictionary];
[self.view addConstraints:constraints];
You can change metrics and positioning and stuff like that too, of course.

Buttons and auto layout

I want to build such interface:
With auto layout disabled, I successfully created those 6 buttons and well adjusted them through code in function of screen's height. However, when disabling auto layout, all other controllers become "messy" so I tried to create/adjust those buttons with auto layout enabled. And there is NO WAY to achieve such interface with auto layout enabled. My question is, is there any trick, solution to adjust those 6 buttons with auto layout enabled? Or perhaps there is a library? I'm really stacked.
Thank you for your help.
I see you found an answer, but I'll post mine anyway, because it uses a different approach. Trying to get the constraints correct in IB (in iOS 6) when there are so many dependencies among the 6 buttons is difficult (because of the constraints the system adds for you in IB), so I did it in code. I did it in such a way that the buttons take the whole screen in any size screen or any orientation without having to check the screen size:
#interface ViewController ()
#property (strong,nonatomic) NSMutableDictionary *viewsDict;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.viewsDict = [NSMutableDictionary dictionary];
for (int i=1; i<7; i++) {
UIButton *b = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[b setTitle:[NSString stringWithFormat:#"Button%d",i] forState:UIControlStateNormal];
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.viewsDict setObject:b forKey:[NSString stringWithFormat:#"b%d",i]];
[self.view addSubview:b];
}
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[b1][b2(==b1)]|" options:0 metrics:nil views:self.viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[b3][b4(==b3)]|" options:0 metrics:nil views:self.viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[b5][b6(==b5)]|" options:0 metrics:nil views:self.viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[b1][b3(==b1)][b5(==b1)]|" options:0 metrics:nil views:self.viewsDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[b2][b4(==b2)][b6(==b2)]|" options:0 metrics:nil views:self.viewsDict]];
}
With autolayout, you will definitely want to wrap those six views in another UIView to prevent layout interactions between the buttons and other views. If that UIView has a fixed height and width, it might be as simple as that.

ios UIViewController programmatically positioning buttons not working

I would like to evenly space four buttons across a view. In the storyboard I have positioned the buttons in a portrait view so the spacing is correct. But I did not find the correct constraint settings to make the buttons space themselves evenly for any view width (for portrait iPad or landscape orientations). So, I added the following code snippet that moves the buttons to desired locations using the 1st and 4th buttons as the anchors:
// evenly space the buttons
CGPoint leftPoint = self.button1.center;
CGPoint rightPoint = self.button4.center;
CGFloat width = rightPoint.x - leftPoint.x;
leftPoint.x += width / 3;
rightPoint.x -= width / 3;
self.button2.center = leftPoint;
self.button3.center = rightPoint;
The positioning code is working fine, but my difficulty is finding the best place to make the adjustments. - (void)viewDidAppear:(BOOL)animated
seems to be the best spot. However, if I seque to a different view, when I return to this view the buttons will have reverted to their initial (storybaord constraint) specified positions. The viewDidAppear code will get called again but it does not succeed at moving the buttons. It is as if their positions are locked at that point in time.
I guess my primary question is if there is a way to use constraints to achieve the even spacing I am after. Or secondary question is how to override the auto positioning of those two buttons.
This is a relatively hard thing to do using layout constraints, and it depends on exactly what you want. I have an example here that creates 4 buttons (in code) along with 5 labels that are used as spacers between the buttons. The buttons' sizes are determined by their intrinsic content size, and the spacing among the buttons and between the buttons and the sides of the containing view are all the same.
-(void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary *viewsDict = [NSMutableDictionary dictionary];
NSArray *titles = #[#"Short",#"Longer",#"Short",#"The Longest"];
for (int i=1; i<5; i++) {
UIButton *b = [UIButton buttonWithType:1];
[b setTitle:titles[i-1] forState:UIControlStateNormal];
[b setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:b forKey:[NSString stringWithFormat:#"b%d",i]];
}
for (int i=1; i<6; i++) {
UILabel *l = [[UILabel alloc ]init];
[l setTranslatesAutoresizingMaskIntoConstraints:NO];
[viewsDict setObject:l forKey:[NSString stringWithFormat:#"l%d",i]];
}
for (id obj in viewsDict.allKeys)
[self.view addSubview:viewsDict[obj]];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|[l1][b1][l2(==l1)][b2][l3(==l1)][b3][l4(==l1)][b4][l5(==l1)]|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:viewsDict];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[b1]-|"
options:0
metrics:nil
views:viewsDict];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints2];
}
The spacing of the buttons will automatically adjust when the view size changes, as on a rotation.
The solution I was led to is to programmatically add constraints to the two middle buttons (button2 & button3) that position them horizontally relative to the middle of the view. These two constraints allowed me to completely remove the manual positioning code. The answer to Evenly space multiple views within a container view helped get me on the right track.
- (void)viewDidLoad
{
...
self.button2.translatesAutoresizingMaskIntoConstraints = NO;
self.button3.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.button2 attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:0.667 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.button3 attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.333 constant:0]];
For almost any case of layout problem use layoutSubviews method, it's the place to do it.

Resources