Question

I am programmatically building a view and using autolayout, no interface builder at all. In a custom ScrollView controller, I am adding a UILabel and a UIButton as subviews. I want to align the label to the left of the screen, and the button to the right of the screen. For some reason, my button only aligns to the left of my scrollview. I've pared down my code so that it's only these two labels and I can't understand why it won't align to the right.

HWScrollViewController.m (How I'm initializing the main scroll view)

- (void)loadView
{
    self.scrollView = [[UIScrollView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];

    self.scrollView.delegate = self;

    self.view = self.scrollView;
}

HWListingDetailViewController.m

- (void)viewDidLoad
{
[super viewDidLoad];

UILabel *priceLabel = [[UILabel alloc] init];
UIButton *favouriteButton = [UIButton buttonWithType:UIButtonTypeContactAdd];

[priceLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[favouriteButton setTranslatesAutoresizingMaskIntoConstraints:NO];

[priceLabel setText:@"$125.00"];
[favouriteButton setTitle:@"Add to Favourites" forState:UIControlStateNormal];

[self.view addSubview:priceLabel];
[self.view addSubview:favouriteButton];

[self.view addConstraints:@[
    [NSLayoutConstraint constraintWithItem:priceLabel 
                                 attribute:NSLayoutAttributeCenterY
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:self.view 
                                 attribute:NSLayoutAttributeCenterY 
                                multiplier:1 
                                  constant:0],

    [NSLayoutConstraint constraintWithItem:priceLabel 
                                 attribute:NSLayoutAttributeLeft 
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:self.view 
                                 attribute:NSLayoutAttributeLeft 
                                multiplier:1 
                                 constant:5],

    [NSLayoutConstraint constraintWithItem:favouriteButton 
                                 attribute:NSLayoutAttributeCenterY 
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:self.view 
                                 attribute:NSLayoutAttributeCenterY 
                                multiplier:1 
                                  constant:0],

    [NSLayoutConstraint constraintWithItem:favouriteButton 
                                 attribute:NSLayoutAttributeRight 
                                 relatedBy:NSLayoutRelationEqual 
                                    toItem:self.view 
                                 attribute:NSLayoutAttributeRight 
                                multiplier:1 
                                  constant:5],

}

Screenshot of problem

As you can see, the green price label is aligned correctly, but the red button is way off of the lefthand side of the screen. (I gave it 5 pixels of offset to show where it was.) So, why is the right side of the scrollview actually the left side? How can I properly align to the right of the screen? Where did I go wrong? This is driving me crazy!

Thanks for any help!

Final layout images: I'm hoping for the final layout to be something like this: Final Layout Portrait

and I would expect it to look like this if rotated to landscape: Final Layout Landscape

Was it helpful?

Solution

The number of constraints is not an issue. Both UILabel and UIButton will determine their size based on their intrinsicContentSize and since you have constraints for the position, it should have all the information it needs for layout.

However, when it comes to autolayout, UIScrollViews behave in a unique way. The best description comes from this technical note. There are two options available including examples, but heres a summary of each.


Mixed Approach

You just need to add a UIView to the UIScrollView then add and position all your subviews in the UIView. This requires you manually setting the frame of the UIView and the contentSize on the UIScrollView.

This is probably the easiest to use with the layout your trying to achieve. However, if the contentSize can change, you'll have to manually calculate and update the size.


Pure Auto Layout Approach

This option uses your autolayout constraints to determine the contentSize of the UIScrollView. This requires constraints going to all four edges of the UIScrollView and can not rely on the size of the UIScrollView.

This option is tougher to use since you need to make sure you have enough constraints. In your case, you'll run into issues because there are no constraints to the top and bottom of the UIScrollView and there are no constraints that can be used to determin the width of the UIScrollView. However, this option is amazing when you have to deal with dynamic content as it will resize the contentSize as needed.


Personally, I would go with the Pure Auto Layout Approach. It's ability to handle dynamic content sizes makes the extra constraint setup worth it.

If you post what you want the final layout to be, I'll update my answer to reflect that.


Update

Based on the images you posted, this is the way I would organize the subviews using the Pure Auto Layout Approach. The main difference is that the UIScrollView is now a a subview of the UIViewControllers view.

- UIView                                            (self.view)
  - UIScrollView                                    (scrollView)
    - UIView                                        (contentView)
      - UIImageView, UIButtons, UILabels, etc.

scrollView needs constraints so its edges are 0px from self.view.

contentView needs constraints so its edges are 0px from scrollView and that its width equals self.view. This is so the contentSize of the scrollView updates when you rotate the device.

Next just position all your images and labels the way you want. The label will need to be constrained to the left and right so it can calculate its height. The important thing to note is contentView will determine its height based on the constraints for its subviews, so you will need constraints "linking" the top and bottom of the contentView. A simeple example would look like this.

  • contentView top to imageView top
  • imageView height == some value
  • imageView bottom to label top
  • label bottom to contentView bottom

OTHER TIPS

You don't have enough constraints for the button. Actually, you haven't got enough constraints for the label either, but that won't necessarily stop your button aligning to the right of the screen.

You always must have constraints which specify how a view behaves in both horizontally and vertically. Usually this means a minimum of four constraints - x, y, width and height. If you don't, iOS will guess for you and chances are it'll be wrong. You'll probably notice a few warnings get thrown up in the Xcode console.

For this problem, it really depends on exactly how you want your view to look, but the simplest solution is to just set a width constraint for the button. You can do that like this:

[NSLayoutConstraint constraintWithItem:favouriteButton 
                             attribute:NSLayoutAttributeWidth  
                             relatedBy:NSLayoutRelationEqual 
                             toItem:nil 
                             attribute:NSLayoutAttributeNotAnAttribute 
                             multiplier:1.0 
                             constant:64.0];

Where 64.0f if the width of the button.

If you want the button to be to the right of the label and share the same width (ie, each taking up 50% of the screen), you can do this:

    [NSLayoutConstraint constraintWithItem:favouriteButton
                                 attribute:NSLayoutAttributeLeading
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:priceLabel
                                 attribute:NSLayoutAttributeTrailing
                                multiplier:1.0f
                                  constant:0.0f];

    [NSLayoutConstraint constraintWithItem:favouriteButton
                                 attribute:NSLayoutAttributeWidth
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:priceLabel
                                 attribute:NSLayoutAttributeWidth
                                multiplier:1.0f 
                                  constant:0.0f];

All the above two constraints do is set the trailing edge of the label to the leading edge of the button and then says that both views must be the same width.

You should also really add height constraints to both views (or top and bottom constraints). I know you've got a vertical centering constraint, but that doesn't specify its size. iOS may work it out by content height, but chances are it'll throw up warnings in the console.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top