2
votes

I'm trying to align two to three buttons horizontally in a view. For simplicity, I'll show my attempt of aligning two buttons.

This works for buttons that have a short title:

@"H:|-10-[questionButton1(questionButton2)]-5-[questionButton2]-10-|"

enter image description here

But as soon as one of the buttons gets a bit longer title, it breaks like this: enter image description here

What I ended up doing is calculating width of each button and then if button1 width is greater than half of the view and greater than button2 width, I've used:

@"H:|-10-[questionButton1(==btn1width)]-5-[questionButton2(>=btn2width)]-10-|"

It kind of works but I don't really like the look of my code with this kind of calculations. Just imagine complexity it adds with the third button. Also, there is a problem if both buttons have pretty long title in which case I would have to figure out if I should reduce the font size to make everything fit.

I'm posting this here because I might be missing some magical thing regarding autolayout since I only started using it in code today. Any kind of help would be greatly appreciated.

--- UPDATE (clarification) ---

I want the buttons to split evenly considering the margins (10 on the outside and 5 between buttons). Ideally they should be the same width if the text size would fit their default size (50%:50% for two buttons and 33%:33%:33% for three buttons). In case the button title exceeds that perfect width, the button should extend its width if it is allowed by other buttons (if others can shrink). If there is no extension or shrinking possible, the big button should reduce font size and repeat the procedure (check if other buttons can shrink). Yeah, I know, I'm asking for a lot :)

how it looks when it works

--- UPDATE ---

@Sikhapol's answer helped me solve it. I've added a few things to reduce font size, add padding and make button titles go into multiple lines if the text doesn't fit:

btn.contentEdgeInsets = UIEdgeInsetsMake(0, 5, 0, 5);
btn.titleLabel.adjustsFontSizeToFitWidth = YES;
btn.titleLabel.numberOfLines = 0;
btn.titleLabel.minimumScaleFactor = 0.7;

End result: end result

3
It's not clear what you want your buttons to look like. Do you want them each to have enough width to display their title, but still expand to the width of the view (minus the 10-5-10 padding)? Do you want them to both have the same width, but reduce font size if necessary (I'm pretty sure there's no way to do that in any simple way without calculating the title sizes)?rdelmar

3 Answers

9
votes

Use Content Compression Resistance Priority!

You can tell auto layout to try to maintain the equal width of the two labels as best as it can. But you tell it that it's more important to let one of them grow bigger to fit the content inside.

To do this, set priority of the equal width constraint to be lower than the content compression resistance priority of the labels (or buttons).

- (void)viewDidLoad {
    [super viewDidLoad];

    UILabel *label1 = [[UILabel alloc] init];
    label1.text = @"this seems";
    label1.backgroundColor = [UIColor orangeColor];
    label1.translatesAutoresizingMaskIntoConstraints = NO;

    UILabel *label2 = [[UILabel alloc] init];
    label2.text = @"completely fine";
    label2.backgroundColor = [UIColor orangeColor];
    label2.translatesAutoresizingMaskIntoConstraints = NO;

    [self.view addSubview:label1];
    [self.view addSubview:label2];

    NSDictionary *views = NSDictionaryOfVariableBindings(label1, label2);

    NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-10-[label1(label2)]-5-[label2]-10-|"
                                                                             options:NSLayoutFormatAlignAllCenterY
                                                                             metrics:nil
                                                                               views:views];

    // Find the equal width constraint and set priority to high (750)
    for (NSLayoutConstraint *constraint in horizontalConstraints) {
        if (constraint.firstAttribute == NSLayoutAttributeWidth) {
            constraint.priority = UILayoutPriorityDefaultHigh;
        }
    }

    [self.view addConstraints:horizontalConstraints];

    // Set content compression resistant to required (1000)
    [label1 setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];
    [label2 setContentCompressionResistancePriority:UILayoutPriorityRequired forAxis:UILayoutConstraintAxisHorizontal];

    // The below code is here to add the vertical center constraints. You can ignore it.
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1
                                                          attribute:NSLayoutAttributeCenterY
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeCenterY
                                                         multiplier:1
                                                           constant:0]];

    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2
                                                          attribute:NSLayoutAttributeCenterY
                                                          relatedBy:NSLayoutRelationEqual
                                                             toItem:self.view
                                                          attribute:NSLayoutAttributeCenterY
                                                         multiplier:1
                                                           constant:0]];
}

So if the content can fit inside those labels:

fit

But if one of them grow longer:

expand

Content Compression Resistance Priority is a way to tell the auto layout that how far you want the component to maintain it's intrinsic size (thus the name compression resistance).

This approach can also be achieved more easily in the IB. The content resistance priority can be set in the Size Inspector tab (cmd + opt + 5).

2
votes

If you're using Auto Layout, you can simply use a constraint to ensure that your buttons are always aligned, either vertically or horizontally. In order to align them horizontally (ie align their y values to be the same), simply select the two buttons by holding command and clicking on them individually: enter image description here

They will appear in Storyboard with selector indicators around them. Now go to the bottom right corner and choose to align their "Vertical Centers". Aligning their vertical centers will align them horizontally (based on your diagramming).

enter image description here

This ensures that they will always be aligned horizontally.

To fix your problem about the text expansion, one way off the top of my head I can think of to get around that is to create a UIView and then putting a UILabel inside to simulate a button. You would have to link up to the view to some IBOutlet to get when it pressed and link that to the function you want it to perform. But UILabel has attributes you can set in Storyboard shown here with the Attributes Inspector:

enter image description here

If you choose "Minimum Font Size", set that value, then your text will shrink automatically as it fills up the allotted space as seen here:

enter image description hereenter image description here

0
votes

As the text grows to fill its width, you end up with a constraint ambiguity. There's no telling what will happen! You need to use constraint priorities and inequalities (and perhaps altered compression resistance) to resolve this.

Here's code where I disambiguate between two labels so that one can grow at the expense of the other:

    let p = self.lab2.contentCompressionResistancePriorityForAxis(.Horizontal)
    self.lab1.setContentCompressionResistancePriority(p+1, forAxis: .Horizontal)

But I also needed to use inequalities to set the widths and spacing originally:

    self.view.addConstraints(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "H:[v1(>=20)]-(>=20)-[v2(>=20)]", options: nil, metrics: nil, views: d)
    )