iOS: Simple AutoLayout with 1 Label and 3 Buttons - ios

I'm playing around with AutoLayout and am really banging my head against the wall for what seems like it ought to be an extremely simple solution.
I've got a vertical column of controls: 1 label and 3 buttons.
I want the label to be 40 pixels(points) tall and auto-size its width based on the width of its superview (standard spacing on left, top and right).
I want the 3 buttons to line up vertically below that label.
I would like their widths to auto-size just like the label.
I would like their spacing to be standard (aqua?) spacing (8 points, right?).
I would like the 3 buttons to be the same height.
I can get what I want to happen to work, but I keep getting errors in the console at runtime, and I'd like to figure out WHY I'm getting them and HOW to avoid getting them. I've watched the WWDC videos on AutoLayout, and here's what I've tried so far:
UILabel *label = [self Label];
MMGlossyButton *button1 = ...
MMGlossyButton *button2 = ...
MMGlossyButton *button3 = ...
[[self view] addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[label]-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(label)]];
[[self view] addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[button1]-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(new)]];
[[self view] addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[button2]-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(existing)]];
[[self view] addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[button3]-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(provider)]];
[[self view] addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[label(40)]-[button1(>=25)]-[button2(==button1)]-[button3(==button1)]-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(label, button1, button2, button3)]];
So, this works for displaying the view in a dynamically-sized way, but the following error pops up in the console:
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want.
Try this:
(1) look at each constraint and try to figure out which you don't expect;
(2) find the code that added the unwanted constraint or constraints and fix it.
(Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you
don't understand, refer to the documentation for the UIView
property translatesAutoresizingMaskIntoConstraints)
// A whole bunch of constraint stuff that doesn't appear to be important...
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7554c40 V:[MMGlossyButton:0x7554b40(99)]>
So, the last bit appears to indicate that the first button I have on the view is statically sized to 99 points tall.
Which is the size it has on the view.
Which is completely arbitrary.
Which I don't want assigned, but can't figure out a way to un-assign it.
Although I'm getting what I want (sort of, eventually), it seems to be a REALLY roundabout way to accomplish something that's pretty simple. Am I missing something basic about AutoLayout, or does its power require such complexity?

You are encountering errors because you are mixing and matching constraints generated in code with constraints added by interface builder. Interface builder doesn't let you generate an ambiguous layout, so almost by definition if you add additional constraints, you will get an "Unable to simultaneously satisfy" error, which is the downfall of many a marriage.
To resolve this you either need to define all the constraints you need in interface builder, or you need to mark specific ones as outlets and remove them in code before adding your own.
In your case, the constraints are simple enough to create in IB.
You can pin to a specific height by using this button in IB while your label is selected:
The one in the middle, that looks like a girder. This gives you the following useful menu:
Choosing one of these allows you to create a new constraint against the label, which you can then edit by selecting it:
You can then add your buttons, select all three of them and, using the same menu, create an equal heights constraint.
The constraints created in IB aren't particularly flexible, so if you do decide you need to create or modify them in code, it is best to create outlets to the particular constraints, and then either remove and re-create them, or modify the constant value at run time.

Related

Align Leading Edges of View with constraints programmatically

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];

NSLayoutConstraint - I want to create side margins

I have a view that is in a container
In some of the view controllers that I place in this container, i want to have global side margins
but I don't know how to add them to the container.
I mean I don't know how to define the NSLayoutConstraint to do what I want.
I want side margins of 20pts from each side, left and right of the container, in regards to its superview
It should look something like that:
// Get view and bind that
UIView * view = childViewController.view;
NSDictionary *views = NSDictionaryOfVariableBindings(view); // #"view" : view
// add constraint to view with margins - -value- to supertview - | in horizontal axis
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-20.0-[view]-20.0-|" options:0 metrics:metrics views:views]];
// The same for vertical axis
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-20.0-[view]-20.0-|" options:0 metrics:metrics views:views]];
Also, take a look at this tutorial: http://commandshift.co.uk/blog/2013/01/31/visual-format-language-for-autolayout/ . It looks nice.
Good Luck!
You can solve this with a more intuitive code than the official API (of which the visual format is the easiest to grasp, but still not very intuitive IMHO).
If you use a third party category like https://github.com/jrturton/UIView-Autolayout (which I recommend you do), this is solved with as little code as this:
[yourView pinToSuperviewEdges:JRTViewPinLeftEdge|JRTViewPinRightEdge inset:20.0];
UPDATE
Since your comment states that the right edge does not work, you can try setting the edge constraints individually to debug it, like this:
[yourView pinEdge:NSLayoutAttributeLeft toEdge:NSLayoutAttributeLeft ofView:yourSuperView inset:20];
[yourView pinEdge:NSLayoutAttributeRight toEdge:NSLayoutAttributeRight ofView:yourSuperView inset:40];
Change the inset around on the second constraint to see if you get it right. If it suddenly looks right with an inset of 50, you know that the superview is too wide (by 30 pixels in this case).

Using Auto Layout with Dynamic UITextView, UIImageView, and UILabel Heights

I am trying to create a view that contains one image view with 2 labels and text views. The contents of each element will be read in from an SQLite database, so I would like to use Xcode 5's auto layout to change the heights of each UI element and the distance between them based on the content of each to use the screen space as efficiently as possible. I have a few conditions that I am trying to enforce via auto layout in the interface builder, but I am not sure how to implement them:
The distance between a label and the text view directly below it will stay the same
The widths of all elements will stay the same and they will all be centered horizontally
Only heights and y values of the elements' frames will change
If the content heights of the text views exceed the amount of screen height they can be displayed in, the image view's height should be decreased to a minimum value, then the heights of the text views should be decreased
The distance between each element should be equal (excluding distances between associated labels and text views)
I would be able to do all of this programmatically, but I would like to use the auto layout so the view can easily adapt to changes in screen size and to prevent bugs. I have very little experience with auto layout and I am having trouble with the complex specifications I need in this situation. To make what I intend to create clearer, here is a screenshot of the .xib file:
Thanks for the help!
http://www.techotopia.com/index.php/Implementing_iOS_6_Auto_Layout_Constraints_in_Code
please reffer this website,
may be got your solution
Take a look Autolayout Visual Format Language. They talk about this in a few of the auto layout WWDC videos from last year.
This isn't complete, but you will need to define the constraints both vertically and horizontally. This is complete but hopefully gives you a ruff idea to get your started.
// Vertical alignment should be centered
NSArray *constraints = [NSLayoutConstraint
constraintsWithVisualFormat:#"V:|[_imageView]-[_label1]-[_textBlock1]-[label2]-[textBlock2]-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil
views:viewsDictionary];
// Tell the views/text blocks to take the entire width. The labels will be fine centered on them I think
NSArray *constraints = [NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-[_imageView]-|"
options:NSLayoutFormatAlignAllCenterY
metrics:nil
views:viewsDictionary];
NSArray *constraints = [NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-[_textBlock1]-|"
options:nil
metrics:nil
views:viewsDictionary];
NSArray *constraints = [NSLayoutConstraint
constraintsWithVisualFormat:#"H:|-[_textBlock2]-|"
options:nil
metrics:nil
views:viewsDictionary];

I'm in purpose of hiding UIPickerView below the screen using constraints, can I ignore this debugger message?

I want to hide UIPickerView below the screen, in this case 3.5 inch display. after learning about constraint value, finally I know what number should I put for NSLayoutAttributeTop.
here's my complete code :
NSLayoutConstraint *constraint = [NSLayoutConstraint
constraintWithItem:_pickerView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0f
constant:416.f];
[self.view addConstraint:constraint];
and when I run into my iDevice it works like I expected, except... the debugger console.. it gives me this message :
2013-09-19 12:34:01.850 Constrain2[3546:c07] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x75490d0 h=--& v=--& V:[UIView:0x715a2f0(416)]>",
"<NSLayoutConstraint:0x7159950 V:[UIPickerView:0x715a030(216)]>",
"<NSLayoutConstraint:0x71597c0 V:|-(416)-[UIPickerView:0x715a030] (Names: '|':UIView:0x715a2f0 )>",
"<NSLayoutConstraint:0x715a7a0 UIPickerView:0x715a030.bottom == UIView:0x715a2f0.bottom>"
)
when I changed the constant value into = 200.f which taken from 416 (superview) - 216 (uipickerview), debugger console is clear. no message there.
is it error message or just a warning? can it be ignored? is it possible to hide that UIPickerView below the screen without having that message appear on debugger console?
it seems that message won't appear when UIPickerView placed properly in its position. that is constant: 200.f
thank you.
No, you should not ignore that -- the system will try to fix it by removing a constraint, but I don't know if it will always remove the same one, and give you the result you want. You appear to be adding a constraint that conflicts with one you already have (I commented on that same problem in my answer to this previous question of yours). When you add a new constraint in code, you need to remove one (or more) that you made in IB that will conflict with it. However, to do what you're trying to do, you shouldn't even be adding a new constraint, you should be modifying the one you made in IB. From the error message it looks like you have a constraint from the bottom of the picker to the bottom of its superview (with 0 length). You should make an IBOutlet to that constraint (lets call it bottomCon for example), and then just modify its constant parameter in code:
self.bottomCon.constant = -216;
In any case, if you're making a constraint to a view that's near the bottom of the screen, you should make the constraints to the bottom, not the top like you do in your question. If you make the constant to the top, it won't be correct in a different screen size or orientation.
Add the line
_pickerView.translatesAutoresizingMaskIntoConstraints = NO;
before you add the constraint.

Setting constraints programmatically

I'm experimenting with how to use UIScrollView. After much trouble, I finally got the hang of it. But now I've seem to hit another snag.
In this simple app, I have a scroll view with and in order for it to work, I have to set the view's bottom space to scrollview constraint to 0 as described here and it works fine. I'm doing it through the IB.
Now I've come across a scenario where I have to do that part programmatically. I've added the below code in the viewDidLoad method.
NSLayoutConstraint *bottomSpaceConstraint = [NSLayoutConstraint constraintWithItem:self.view
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.scrollView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0];
[self.view addConstraint:bottomSpaceConstraint];
But it doesn't seem to work. It outputs the following message in the console window adn i don't know what to make of it.
Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"",
""
)
Can someone please tell me how to do this? I've also attached a demo project here so that you can get a better idea on the issue.
UPDATE:
First off thanks for the responses. Using the ways mentioned in the answers I was able to get it working. However ina slightly different scenario its not. Now I'm trying to load a view onto a viewcontroller programatically.
If I may explain further. There are 2 view controllers. First one being a UITableViewController and the second one a UIViewController. Inside that a UIScrollView. Also There are multiple UIViews and some of those views' height exceeds the normal height of the screen.
The UITableViewController displays a list of options. Based on the user's selection, a particular UIView out of the lot will be loaded into the UIViewController with the UIScrollView.
In this scenario, the above method doesn't work. The scrolling isn't happening. Do I have to do something different since I'm loading the view separately?
I've uploaded a demo project here so that you can see it in action. Please see the Email.xib and select Email from the table view list.
Based upon a review of your code, a few comments:
You generally don't need to adjust constraints when a view appears. If you find yourself doing this, it often means that you haven't configured your storyboard correctly (or at least, not efficiently). The only time you really need to set/create constraints is either (a) you're adding views programmatically (which I'm suggesting is more work than it's worth); or (b) you need to do some runtime adjustment of constraints (see third bullet under point 3, below).
I don't mean to belabor it, but your project had a bunch of redundant code. E.g.
You were setting the frame for the scroll view, but that is governed by constraints, so that does nothing (i.e. when the constraints are applied, any manually set frame settings will be replaced). In general, in auto layout, don't try changing frame directly: Edit the constraints. But, no changing of constraints is needed at all anyway, so the point is moot.
You were setting the content size for the scroll view, but in auto layout, that, too, is governed by constraints (of the subviews), so that was unnecessary.
You were setting constraints for the scrollview (which were already zero), but then you weren't adding the view from the NIB into the scrollview, defeating any intent there, too. The original question was how to change the bottom constraint of the scroll view. But the bottom constraint for that is already zero, so I see no reason to set it to zero again.
I'd suggest a more radical simplification of your project:
You're making life much harder on yourself by storing your views in NIBs. It's much easier if you stay within the the storyboard world. We can help you do the NIB stuff if you really need to, but why make life so hard on yourself?
Use cell prototypes to facilitate the design of the cells in your table. You can also define the segues to go from the cells to the next scene. This eliminates any need to write any didSelectRowAtIndexPath or prepareForSegue code. Clearly, if you have something you need to pass to the next scene, by all means use prepareForSegue, but nothing you've presented thus far requires that, so I've commented it out in my examples.
Assuming you were looking for a practical example of programmatically changing constraints, I've set up the scene so that the text view will change its height programmatically, based upon the text in the text view. As always, rather than iterating through the constraints to find the one in question, when altering an existing constraint that IB created for me, I think it's far more efficient to set up an IBOutlet for the constraint, and edit the constant property for the constraint directly, so that's what I've done. So I set up the view controller to be the delegate of the text view, and wrote a textViewDidChange that updated the text view's height constraint:
#pragma mark - UITextViewDelegate
- (void)textViewDidChange:(UITextView *)textView
{
self.textViewHeightConstraint.constant = textView.contentSize.height;
[self.scrollView layoutIfNeeded];
}
Note, my text view has two height constraints, a mandatory minimum height constraint, and a medium priority constraint that I change above based upon the amount of text. The main point is that it illustrates a practical example of changing constraints programmatically. You shouldn't have to muck around with the scrollview's bottom constraint at all, but this is shows a real-world example of when you might want to adjust a constraint.
When you add a scrollview in IB, it will automatically get all the constraints you need. You probably don't want to be adding a constraint programmatically (at least not without removing the existing bottom constraint).
Two approaches might be simpler:
Create an IBOutlet for your existing bottom constraint, say scrollViewBottomConstraint. Then you can just do
self.scrollViewBottomConstraint.constant = 0.0;
Or create your view initially in IB where the bottom constraint is 0.0 and then you don't have to do anything programmatically at all. If you want to layout a long scrollview and it's subviews, select the controller, set it's simulated metrics from "inferred" to "free form". Then you can change the size of the view, set the scrollview's top and bottom constraints to be zero, layout everything you want inside the scroll view, and then when the view is presented at runtime, the view will be resized appropriately, and because you've defined the scrollview's top and bottom constraints to be 0.0, it will be resized properly. It looks a bit odd in IB, but it works like a charm when the app runs.
If you're determined to add a new constraint, you could either programmatically remove the old bottom constraint, or set the old bottom constraints' priority down as low as possible, and that way your new constraint (with higher priority) will take precedence, and the old, low-priority bottom constraint will gracefully not be applied.
But you definitely don't want to just add a new constraint.
It's possible to create outlets to represent layout constraints in your view controller. Just select the constraint you want in interface builder (e.g. via "select and edit" on the measurements pane of the view you are arranging). Then go to the outlets pane and drag a "New Referencing Outlet" to your code file (.h or .m). This will bind the constraint to an NSLayoutConstraint instance that you can access from your controller and adjust dynamically on the fly (generally via the constant property, which is poorly named because it's not a constant at all).
(Note that in XCode 6 you can double-click the constraint to select it for editing.)
Be careful when adjusting the layout in interface builder, however, as you may end up deleting the constraint and have to re-bind it to the outlet.
Looking at the console information, i feel that you are creating ambiguity when you add two same type of constraint.
So instead of creating and adding new constraint, try updating the previous constraint that is already in the constraints array.
for(NSLayoutConstraint *constraint in self.view.constraints)
{
if(constraint.firstAttribute == NSLayoutAttributeBottom && constraint.secondAttribute == NSLayoutAttributeBottom &&
constraint.firstItem == self.view && constraint.secondItem == self.scrollView)
{
constraint.constant = 0.0;
}
}
Hope this helps
Even Rob's answer will work!
You can use https://github.com/SnapKit/Masonry for adding constraints programmatically.
It is power of AutoLayout NSLayoutConstraints with a simplified, chainable and expressive syntax. Supports iOS and OSX Auto Layout.
UIView *superview = self.view;
UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[superview addConstraints:#[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:padding.top],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:padding.left],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:-padding.bottom],
[NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:superview
attribute:NSLayoutAttributeRight
multiplier:1
constant:-padding.right],]];
in just few lines
Heres the same constraints created using MASConstraintMaker
UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
make.left.equalTo(superview.mas_left).with.offset(padding.left);
make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];
Or even shorter
[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
make.edges.equalTo(superview).with.insets(padding);
}];
do your best is sort ;)

Resources