Auto layout - complex constraints - ios

I'm trying to display content in a table cell using auto layout, programmatically. I'd like for the content to display as follows:
[title]
[image] [date]
[long string of text, spanning the width of the table, maximum of two lines]
My code looks like this:
-(NSArray *)constraints {
NSMutableArray * constraints = [[NSMutableArray alloc]init];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_titleLabel, _descriptionLabel, _dateLabel, _ratingBubbleView);
NSDictionary *metrics = #{#"padding":#(kPadding)};
NSString *const kVertical = #"V:|-(>=0,<=padding)-[_titleLabel]-(<=padding)-[_ratingBubbleView]-(<=padding)-[_descriptionLabel]-(>=0,<=padding)-|";
NSString *const kVertical2 = #"V:|-(>=0,<=padding)-[_titleLabel]-(<=padding)-[_dateLabel]-(<=padding)-[_descriptionLabel]-(>=0,<=padding)-|";
NSString *const kHorizontalDescriptionLabel = #"H:|-padding-[_descriptionLabel]-padding-|";
NSString *const kHorizontalTitleLabel = #"H:|-padding-[_titleLabel]";
NSString *const kHorizontalDateLabel = #"H:|-padding-[_ratingBubbleView]-padding-[_dateLabel]";
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:kVertical options:0 metrics:metrics views:viewsDictionary]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:kVertical2 options:0 metrics:metrics views:viewsDictionary]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:kHorizontalDescriptionLabel options:0 metrics:metrics views:viewsDictionary]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:kHorizontalTitleLabel options:0 metrics:metrics views:viewsDictionary]];
[constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:kHorizontalDateLabel options:0 metrics:metrics views:viewsDictionary]];
return constraints;
}
This is the result:

OK, I'm not going to try a fix your code. I'm just going to create constraints that I would use to achieve your layout. I'll put the thought process in comments.
First get a nice vertical layout going...
// I'm just using standard padding to make it easier to read.
// Also, I'd avoid the variable padding stuff. Just set it to a fixed value.
// i.e. ==padding not (>=0, <=padding). That's confusing to read and ambiguous.
#"V:|-[titleLabel]-[ratingBubbleView]-[descriptionLabel]-|"
Then go through layer by layer adding horizontal constraints...
// constraint the trailing edge too. You never know if you'll get a stupidly
// long title. You want to stop it colliding with the end of the screen.
// use >= here. The label will try to take it's intrinsic content size
// i.e. the smallest size to fit the text. Until it can't and then it will
// break it's content size to keep your >= constraint.
#"|-[titleLabel]->=20-|"
// when adding this you need the option "NSLayoutFormatAlignAllBottom".
#"|-[ratingBubbleView]-[dateLabel]->=20-|"
#"|-[descriptionLabel]-|"
Try not to "over constrain" your view. In your code you are constraining the same views with multiple constraints (like descriptionLabel to the bottom of the superview).
Once they're defined they don't need to be defined again.
Again, with the padding. Just use padding rather than >=padding. Does >=20 mean 20, 21.5, or 320? The inequality is ambiguous when laying out.
Also, In my constraints I have used the layout option to constrain the vertical axis of the date label to the rating view. i.e. "Stay in line vertically with the rating view". Instead of constraining against the title label and stuff... This means I only need to define the position of that line of UI once.

Related

Using the autolayout visual format language is it possible to set the width of a view equal to the height of the same view?

Using the autolayout visual format language is it possible to set the width of a view equal to the height of the same view?
Here is what I want to do:
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[stopButton(100)]" options:kNilOptions metrics:nil views:views]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-50-[stopButton(stopButton.height)" options:kNilOptions metrics:nil views:views]];
You would need to create a fixed aspect ratio constraint which, according to the documentation for the visual formatting language, is not possible currently.
One useful constraint that cannot be expressed is a fixed aspect ratio
(for example, imageView.width = 2 * imageView.height). To create such
a constraint, you must use
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:.
Luke Van In's answer on a fixed aspect ratio is a great answer!
Of course, if you simply want to use the same value in multiple places you would modify the code to use a metrics dictionary as follows:
NSDictionary* metrics = #{#"stopButtonSize":#100};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[stopButton(stopButtonSize)]" options:kNilOptions metrics:metrics views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-50-[stopButton(stopButtonSize)]" options:kNilOptions metrics:metrics views:views]];

Displaying views with programmatic auto layout

EDIT
I am using programmatic auto layout and this issue seems to be eluding me,
in this class
#interface FooterButtonView : UIView {
...
}
I am trying to line up two views side by side
- (void)setUpViewWithTwoElements:(UIView*)element1 :(UIView*)element2{
element1.translatesAutoresizingMaskIntoConstraints = NO;
element2.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary* views = #{#"element1":element1, #"element2":element2};
NSDictionary* metrics = #{#"buttonHeight":#30.0};
NSString* horizontalFormatString = #"H:|-[element1]-[element2]-|";
NSString* verticalFormatString = #"V:[element1(buttonHeight)]-|";
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:horizontalFormatString
options: NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:verticalFormatString
options:nil
metrics:metrics
views:views]];
}
however neither elements is being displayed.
in init I am adding both subviews and then calling the above function. Both elements descend from UIButton.
Any suggestions are greatly appreciated. Thank you!
You should see something with the code you posted assuming that the init method that calls this code is itself being called (and FooterButtonView is being displayed with a non-zero size). One thing you're missing though is the relative horizontal sizes of the two views. With the code you have, there's no way for the system to know what size each of the elements should be, just that they should take up the whole width minus the standard spacings. If you want the two views to be the same size, then change to this,
NSString* horizontalFormatString = #"H:|-[element1]-[element2(==element1)]-|";

Using AutoLayout to one label to push another, if its growing

Say i have two labels, close to each ether, and one will maybe grow:
So if the left label will change and grow, i would like the right label to move to the right and give space, but not squeeze, like so:
Normally i just use:
CGFloat width = [self.priceLabel.text sizeWithFont:[UIFont systemFontOfSize:13]].width;
self.myLabel.frame = CGRectMake(self.myLabel.frame.origin.x, self.myLabel.frame.origin.y, width,self.myLabel.frame.size.height);
and move the right label to the end of of the left label,
But i'm using AutoLayout and looking for a way to make it possible
Thanks!!
You can start by trying the visual format:
NSString *visualFormat = #"|-[label1]-[label2]";
NSLayoutFormatOptions options = NSLayoutFormatAlignAllCenterY | NSLayoutFormatDirectionLeftToRight;
NSDictionary *views = NSDictionaryOfVariableBindings(label1, label2);
NSArray *layoutConstraints = [NSLayoutConstraint constraintsWithVisualFormat:visualFormat options:options metrics:nil views:views];
[view addConstraints:layoutConstraints];
If you also want to add a right margin and any available space in the middle you can use
NSString *visualFormat = #"|-[label1]-#1-[label2]-|";
Check the visual format guide for all possible options.

Shifting UIViews with Auto Layout when one ore more views are hidden

I have a view that has an header. This header has 4 views that show images on the right. I call them icons since every of them shows or draw a glyph. Depending on data, icon 2, 3 or 4 may be hidden given me six possible combinations.
Even when hidden, every invisible icon occupy its space, giving one or more "holes" in the visualization.
This is what I'm using right now.
[header addSubview:_label];
[header addSubview:_icon1];
[header addSubview:_icon2];
[header addSubview:_icon3];
[header addSubview:_icon4];
NSDictionary *headerViewDict = NSDictionaryOfVariableBindings(_label, _icon1, _icon2, _icon3, _icon4);
[header addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-2-[_label]-0-[_icon4(>=0,14)]-1-[_icon3(>=0,14)]-1-[_icon2(>=0,14)]-1-[_icon1(14)]-2-|" options:nil metrics:nil views:headerViewDict]];
[header addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_label]|" options:nil metrics:nil views:headerViewDict]];
[header addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_icon1]|" options:nil metrics:nil views:headerViewDict]];
[header addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_icon2]|" options:nil metrics:nil views:headerViewDict]];
[header addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_icon3]|" options:nil metrics:nil views:headerViewDict]];
[header addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_icon4]|" options:nil metrics:nil views:headerViewDict]];
I've just read (https://stackoverflow.com/a/18066138/1360888) that to solve this there are two possibilities over-constrain or change constant.
I'm quite new to autolayout and I have used always Visual Format Language (since I build my view with code only) so I did not understood how to apply that solution to my case.
How can I create a fluid layout for my view?
Note: In my app I have a lot of views like the header visible at the same time, so performance is important.
What I would do is to create an array of icons(as you call them, UIImageViews?). Then update the contents of the array according to your data.
On ViewWillLayoutSubviews
Check the array [1, 2, 3, 4]
Remove all constraints
Set new constraints according to your content array. The important thing here is to check the element of the array as nil. Autolayout does not process the nil elements correctly and fails.
Removing constraints:
//Clear the constraints
for (NSLayoutConstraint *constraint in [self.view constraints]) {
[self.view removeConstraint:constraint];
}
Adding constraints:
if (myCustomView) {//constraintsWithVisualFormat does not support handling nil
//Add constraints for myCustomView Here
}
ViewWillLayoutSubviews
/**
* Update the constraints before laying subviews
*
*/
- (void) viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
//Remove constraints
//Set new constraints
}

iOS Auto Layout: Equal Spaces to Fit Superviews Width [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Autolayout Even Spacing
I'm trying to create a scrollable bar with buttons (similar to a UISegmentedControl). The superview is an UIScrollView. As soon as the buttons don't fit into the scrollview, the scrollview should be scrollable. So far, almost everything works fine:
With a lot of buttons (scrolled to the right):
Not so many buttons:
Now, my goal is that if there is room for all buttons, they should be equally spread across the whole 320px view. How can I define constrains for the spaces in between the buttons?
Right now, I'm using the following constraints (self is a UIScrollView):
UIView *firstButton = self.buttons[0];
[self.buttonConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"|-(5)-[firstButton]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(firstButton)]];
UIView *lastButton = [self.buttons lastObject];
[self.buttonConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"[lastButton]-(5)-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(lastButton)]];
UIView *previousView = nil;
for (UIView *view in self.buttons) {
if (previousView) {
[self.buttonConstraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:#"[previousView]-(5)-[view]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(previousView, view)]];
}
previousView = view;
}
If I change the type of the superview from UIScrollView to an UIView, I get the following result, still not what I want, but at least it looks for the constraint of the last button that ties it to the right edge (makes sense, that this doesn't happen for the scrollview, as the content size is automatically set):
Any ideas?
- (void) viewWillLayoutSubviews {
// UIButton *button1, *button2, *button3, *button 4 ;
// NSMutableArray *constraintsForButtons ;
float unusedHorizontalSpace = self.view.bounds.size.width - button1.intrinsicContentSize.width - button2.intrinsicContentSize.width - button3.intrinsicContentSize.width - button4.intrinsicContentSize.width ;
NSNumber* spaceBetweenEachButton= [NSNumber numberWithFloat: unusedHorizontalSpace / 5 ] ;
[self.view removeConstraints:constraintsForButtons] ;
[constraintsForButtons removeAllObjects] ;
[constraintsForButtons addObjectsFromArray: [NSLayoutConstraint constraintsWithVisualFormat: #"H:|-(space)-[button1]-(space)-[button2]-(space)-[button3]-(space)-[button4]-(space)-|"
options: NSLayoutFormatAlignAllCenterY
metrics: #{#"space":spaceBetweenEachButton}
views: NSDictionaryOfVariableBindings(button1,button2,button3,button4) ] ] ;
[constraintsForButtons addObjectsFromArray: [NSLayoutConstraint constraintsWithVisualFormat: #"V:|[button1]"
options: 0
metrics: nil
views: NSDictionaryOfVariableBindings(button1) ] ] ;
[self.view addConstraints:constraintsForButtons] ;
}
This isn't as pretty as yours, and it assumes there are 4 buttons, but it equally spaces them. That is, the empty spaces between the buttons all have the same width. This does not mean that the distance between the NSLayoutAttributeLeading of button1 and button2 is the same as the distance between button2 & button3's.

Resources