I am not able to achieve the desired layout of the views with the auto layout in >iOS6.
I have UIView1 and UIView3 which are fixed to the parent view (correspondingly with the TopSpaceToSuperview, BottomSpaceToSuperView and FixedHeight) and they behave as expected when the parent view changes the height.
Which constraints should I specify in the IB for the UIView2 if I want it to maintain the same proportional distance to its siblings (UIView1 and UIView3) when the parent view changes the height? (as displayed in the image)
The way to do this is to use invisible "spacer" views between your views.
You can't have relatively sized spaces so use these views instead.
Where the current spaces are place a UIView in each.
Then (in code as you can't do this in IB) set a height constraint between these with the correct multiplier that you want.
i.e.
[NSLayoutConstraint constraintWithItem1:spacer2
attribute:NSLayoutAttributeHeight
relation:NSLayoutRelationEqual
item2:spacer1
attribute:NSLayoutAttributeHeight
multiplier:0.5
constant:0];
Then make the other views "stick" above and below these spacer views with 0 spacing.
Then you just have to hide these views and the auto layout will take care of the rest.
Related
I am having an UIImageView (say imageView1) and a UITextView(say textView1) which have to be displayed vertically (one [imageView1] below the other [textView1]) beginning with the same margin position as of textView1. I have to achieve this through autolayout programmatically.
I know that this can be done by setting the vertical constraints like below for both the views.
NSLayoutConstraint constraintsWithVisualFormat:#"V:|[textView1]"
But the problem I have here is I already have many text views(textView2, textView3) arranged in horizontal before and after this textView1.
I have already added many autolayout constraints to this textView1 through storyboard. Based on the different screen size and orientation the textView1 margin differs as per the constraints that are provided on the storyboard for this.
Now how can I provide the autolayout constraint programmatically in such a way that my imageView1 is to align in par vertically with the same margin as that of textView1?
p.s: imageView1 is created programmatically in code but where as all other views that I mentioned above are created through storyboard.
+ Adding images for easy understanding
In the image, imageView1 is the UI Image. I have created it in storyboard just for understanding purpose but in real it will be created programmatically and this have to be aligned to the margin of UITextView (textView1) present below it.
This is the constraint that I want to create it through programmatically(In case this is the real question here :).
This constraint is to always make sure that imageView1 and textView1 start originating from the same margin.
How to define this constraint programmatically ?
Rather than using the visual format, you can just instantiate a constraint directly, e.g.
[NSLayoutConstraint
constraintWithItem:imageView1 attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:textView1 attribute:NSLayoutAttributeLeading
multiplier:1.0 constant:0.0]
Use Masonry for setting constraints programmatically.It is very easy to use and reduce lots of complexities for the user.
https://github.com/SnapKit/Masonry
...you can try to build an UIView, set the constraints that it will need, and use it as a placeholder for your UIImageViews.(later you can add them inside of such a view) or, by the otherside, using an UICollectionView instead.
You can set the options argument in
constraintsWithVisualFormat:options:metrics:views: check Apple Class Reference.
Your code might be as follows
NSString* leadingConstraintsExpression = #"V:[imageView1][textView1]";
NSDictionary* viewsDictionary = NSDictionaryOfVariableBindings(imageView1,textView1);
NSArray* leadingConstraints = [NSLayoutConstraint
constraintsWithVisualFormat:leadingConstraintsExpression
options:NSLayoutFormatAlignAllLeading
metrics:nil
views:viewsDictionary];
[self.view addConstraints:leadingConstraints];
I am trying to set up the following layout (all vertical centred so I'm ignoring that aspect):
| Label btn View |
|-10-|-----X-----|-10-|--30--|-10-|---Y---|-10-|
A B C D E F G H
A - left edge of superview
B - left hand edge of UILabel
C - right hand edge of UILabel
D - left hand edge of Button
E - right hand edge of Button
F - left hand edge of UIView [view]
G - right hand edge of UIView
H - right edge of superview
The button width is fixed (the 30). Depending on some other factors I add a different sub-views to [view] at run time. The different sub-views have different widths.
I would like Y to scale with the width of the added subview (and preferably respond to changes accordingly).
I would like X to take the remainder of the space, thus making the whole set fill the width of the superview.
~
I am trying to work out how to do this with the storyboard tools (manipulating constraints and frames at runtime would make this a different problem, and an easier one for me).
I currently have spacings set up as:
Label to LHS: 10, priority 1000
Label to Button: 10, priority 1000
Button to View: 10, priority 1000
View to RHS: 10, priority 1000
The subview has a layout like:
|-10-|-fixed width button-|-10-|-fixed width button-|- (no constraint) -|
And also tried:
|-10-|-fixed width button-|-10-|-fixed width button-|-10-|
(Which resulted in a correct size for subview, but [view] just takes up whatever space is left after hugging the UILabel content size.)
I have tried changing the content-hugging and resistance properties of the view (thinking this was the key), but the only outcomes I can get to happen are:
Fixed width View (ignores contained content)
Label shrinks to content size
Specifically, I made the content hugging and compression property of View greater than that of label, thinking that this would cause it to hug the content, but I ended up with the Label shrinking to the width of the content and the View taking up the remainder. I have read this Cocoa Autolayout: content hugging vs content compression resistance priority but either misunderstood it and/or have to consider something else in my solution.
In case it makes a difference, the Label runs onto two lines (sometimes).
With some hints from #rdelmar (thanks!). I managed to find a solution that I'm reasonably happy with.
Basically, it looked like I'm not able to do this in the storyboard, that the subview won't dictate the superview as it is added through code.
So I needed to add some constraints, and crucially (to avoid it conflicting with the subview's original understanding of things:
[subview setTranslatesAutoresizingMaskIntoConstraints:NO];
NSMutableArray * constraints = [NSMutableArray array];
[constraints addObject:[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:view
attribute:NSLayoutAttributeLeftMargin
multiplier:1.0
constant:0]];
[constraints addObject:[NSLayoutConstraint constraintWithItem:subview
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:view
attribute:NSLayoutAttributeRightMargin
multiplier:1.0
constant:0]];
[view addConstraints:constraints];
If someone is able to explain this answer, or indeed improve (e.g. storyboard only) then I think the world would be a better place.
I have a created the following auto layout in Interface Builder:
As you can see I didn't give any fix size to the buttons. I would like to add two button programmatically to get to this result:
Adding the constraints programmatically I know how to do that, at least I know the syntax.
My problem is when to create those buttons?
I create the width constraint based on the width of the button 4. If I do it in viewDidLoad (if I'm not wrong), the auto layout hasn't been set yet so the width (and height) will be wrong.
I thought to do it in viewDidLayoutSubviews but as it's called multiple times when loading the viewController, I get multiple buttons stacked on each other and when I go to landscape more buttons are added..
When should I create those button to have the right sizes?
Auto layout is about rules that hold at all times, not (primarily) about frame sizes at any one moment.
You should not care about getting the frame of button 4 when you set up the constraints for buttons 5 and 6. The constraint that you add for buttons 5 and 6 should refer to button 4's width attribute, not its current width in points. That is, you could create a constraint like this:
NSLayoutConstraint* constraint = [NSLayoutConstraint constraintWithItem:button5 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:button4 attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
constraint.active = YES; // OR: [button5.superview addConstraint:constraint]
That's a constraint that will keep button 5's width the same as button 4's width, even as button 4's width changes. You would do the same for height, and for button 6. Etc.
Put another way, the constraints you create at runtime should be similar to those you would create in IB if you were doing this at design time. It doesn't look to me like you've created explicit, fixed height and width constraints on button 4. You've created relative constraints relating its height and width to other views.
One thing you will have to do: since buttons 2 and 4 have trailing space constraints to the container (or its margins), you will need to remove those constraints when you add buttons 5 and 6. Buttons 2 and 4 would have to have trailing constraints to buttons 5 and 6, respectively, and buttons 5 and 6 would have to have trailing constraints to the container. Actually, you should simplify by getting rid of button 4's trailing constraint to the container and replacing it with a trailing alignment constraint to button 2. Likewise, button 6's trailing edge should be aligned with button 5's, not spaced from the superview's. That way, you only have to remove one constraint (button 2's trailing to superview) and add one (button 5's trailing to superview).
You can create the constraints programmatically in viewDidLoad. If you made an IBOutlet for the buttons, then you can access them and get the size like so:
self.myButton.frame.size.height;
You can use the Autolayout Constraints tool, to make this process easier.
The parent view (green rectangle) contains many subviews.
Lets consider only for one subview it has horizontal position with x offset from 0.
And whenever the parent view's width is changed lets say with factor of a, I need the subview to be positioned with offset equal to a * x
As much I understood NSLayoutConstraint does not allow to set a contraint for red subview's attribute NSLayoutAttributeLeft based on containing green views width NSLayoutAttributeWidth.
Any suggestion/reference how to acheive proportional positioning like described by means of NSLayoutConstraint is welcomed.
You won't be able to lay it out in Interface Builder but you can use the NSLayoutAttributeLeft on the super view in code.
This way you can give it a multiple which will keep the position correct when the superview is updated.
If that doesn't work you can use a "spacer" view.
So you will have them both in the green superview like this...
|[spacerView][redView] //using VFL
Then you can set the width of the spacer view proportionally to the width of the green view.
Just set the spacerView to hidden or give it an alpha of 0.0 so it doesn't show.
As you said, you cannot relate the left attribute of your subview with the width of the superview, but you can relate it to NSLayoutAttributeRight, which has the same value as the width. So you should be able to do it by using a multiplier (and 0 for the constant),
[_greenView addConstraint:[NSLayoutConstraint constraintWithItem:_redView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:_greenView attribute:NSLayoutAttributeRight multiplier:.1 constant:0]];
Of course you need to do the calculation to figure out, based on the width of the green view, what the multiplier should be. So, in my example, the greenView was 200 points wide to start with, and I wanted the red view to be 20 points from the left, so I used 0.1 for the multiplier.
I'm hoping someone can help me out with this problem. I have a very tall view with three different content section. For the sake of simplicity I will say that the UIView in Interface Builder is 1000px tall, each section is 300px tall, and there is a 50px margin between sections. Each section can be visible or hidden depending on the underlying data. So you could have 1,2, and 3 visible. Or 1 and 3. Or 1 and 2. Or just 3. Etc.....
In Interface Builder I set up all the constraints except for the vertical constraints that link the sections together and the constraints that link the sections to the top and bottom of the parent view. Those are all added at run time depending what sections are showing.
//sample code
//add constraint from top of view to first section
NSLayoutConstraint* topConstraint = [NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:section1 attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
[self.view addConstraint:topConstraint];
//add constraint between sections
NSLayoutConstraint* constraint = [NSLayoutConstraint constraintWithItem:section1 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:section2 attribute:NSLayoutAttributeTop multiplier:1.0 constant:50.0];
[self.view addConstraint:constraint];
//add constraint from bottom section to view
NSLayoutConstraint* bottomConstraint = [NSLayoutConstraint constraintWithItem:section2 attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:bottomConstraint];
When all three are visible, everything is fine. However, when I hide one, one or more of the elements on the screen is vertically stretched so that the content size now matches the size 1000px size of the parent view as it is drawn in Interface Builder. At least that's what I think is going on. I would instead expect the parent view to shrink to the size of the content.
Example: sections (300px each) 1 and 2 are showing with a 50px gap and section 3 is hidden. That means that the size of the content is 650px. I would expect AutoLayout to shrink the parent view to 650px. Instead, the view stays at the size drawn in Interface Builder which is 1000px and then AutoLayout stretches the sizes of the content sections to reach the 1000px. If I resize the view in Interface Builder that new size will be the rendered size. Regardless of the amount of sections showing.
I feel like I'm missing something simple but I've spent hours trying to figure this out. Any ideas?
Edit
Some clarifications. There are no height constraints on any of the sections because they can vary in height depending on the amount of data. I just used 300px each to simplify things. And I'm able to hide the sections instead of setting a zero height because in Interface Builder their are no constraints linking the sections. Those constraints are added through code at runtime because at runtime I know what needs to be linked to what. In the end. There is a solid link of vertical constraints from the top of the parent view to the bottom of the parent view. And none of the hidden sections contain vertical constraints.
Edit 2
If I defined a constraint for the height of the parent view, obviously I can modify the constant and manually set the size of view. Is there anyway to find the minimum size of a view based on dynamic content sizes and constraints?