Can't programmatically set NSConstraints on view - ios

I'm trying to do something very simple:
Creating a view from scratch, adding it to the controller's view, and have it stretch edge to edge to its superview, but I get console warning once the app runs, saying the constraints cannot be simultaneously added to the view.
backView = [UIView new];
backView.backgroundColor = [UIColor redColor];
[[self view] insertSubview:backView aboveSubview:tableview];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|-0-[back]-0-|"
options:0
metrics:nil
views:#{#"back": backView}]];
[[self view] addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[back]-0-|"
options:0
metrics:nil
views:#{#"back": backView}]];

When you create a UIView in code it translates auto-resizing mask to constraints.
i.e. it takes the frame you gave it when you created it and the auto-resizing mask and converts them into NSLayoutConstraints.
These auto-generated constraints are then conflicting with the constraints that you are adding manually.
If I'm right then you will be able to fix it by adding the line...
backView.translatesAutoresizingMaskIntoConstraints = NO;
right after the first line and it should fix the problem.

Related

autolayout problems by code?

i use autolayout without sb and xib.But i have some problems recently.
i don't understand which view should i use to implement the following two methods
- (void)addConstraint:(NSLayoutConstraint *)constraint
- (void)addConstraints:(NSArray<__kindof NSLayoutConstraint *> *)constraints
for example,i have a super view as following:
_menuView = [[UIScrollView alloc] init];
_menuView.translatesAutoresizingMaskIntoConstraints = NO;
[self.view addSubview:_menuView];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[_menuView]-0-|" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_menuView)]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[_menuView(40)]" options:0 metrics:nil views:NSDictionaryOfVariableBindings(_menuView)]];
and two subViews:view1,view2.When i use autolayout to add constraints to describe view1 and view2,
[WHICHVIEW addConstraint:<#(nonnull NSLayoutConstraint *)#>];
or
[WHICHVIEW addConstraints:<#(nonnull NSArray<__kindof NSLayoutConstraint *> *)#>];
what should WHICHVIEW should be?
Let me put it another way,is WHICHVIEW depend on the relationship between view1 and view2?what if view1 is not the same hierarchy as view2?
WHICHVIEW should be a parent of [all] the items you are trying to constrain. So if you are adding a number of views to a scrollview and trying to constrain them, with respect to themselves and to the scrollview, the scrollview should be WHICHVIEW. If view1 and view2 are not at all in the same hierarchy, then you cannot add a constraint between the two.
Do not use any WHICHVIEW. Do not call addConstraints:. Call NSLayoutConstraint.activateConstraints instead. It has the advantage that it does all that work for you - it adds the constraints to the correct views, automatically.

Adding a dynamically sized view (height) to a UIScrollView with Auto Layout

Here is my structure of views for this detail view (blogging application, I want to view the entire post which has dynamic height inside of a scrollview):
UIView
-UIScrollView
-SinglePostView (custom view)
-Title
-Subtitle
-Content
Normally to add a 'single post view' to a UIView I simply instantiate it, feed it my Post object and then add a single constraint that pins the width of the singlePostView to the superview, at which point things get laid out nicely. However when I try to add it to a scroll view, it doesn't show up nor does it scroll.
UIScrollView *scrollView = [[UIScrollView alloc] init];
[self.view addSubview:scrollView];
NOBSinglePostView *singlePost = [[NOBSinglePostView alloc] initWithPost:self.post];
[scrollView addSubview:singlePost];
singlePost.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,singlePost);
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[singlePost]|" options:0 metrics: 0 views:viewsDictionary]];
In the structure you are presenting, using AutoLayout, the contentSize of your UIScrollView is driven by the intrinsicContentSize of its subviews, here your SinglePostView.
The problem is, SinglePostView being a subclass of UIView, its intrinsicContentSize is always CGSizeZero when considered by itself. What you need to do is make the intrinsicContentSize of your SinglePostView depend on the intrinsicContentSize of its subviews.
Then, because the subviews of your SinglePostView are UILabels, and because a UILabel's intrinsicContentSize is the smallest size it needs to display its content, your SinglePostView's intrinsicContentSize will be equal to the sum of its subviews intrinsicContentSizes, that is the total size needed to display the content of all three of your labels.
Here is how to do it.
Step 1: Removing all automatically set constraints
First, as you partially did, you need to remove all constraints automatically set by the system.
Assuming you don't have any constraints set in your storyboard or XIB (or you don't even have one of these), just do:
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
singlePost.translatesAutoresizingMaskIntoConstraints = NO;
titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
subtitleLabel.translatesAutoresizingMaskIntoConstraints = NO;
contentLabel.translatesAutoresizingMaskIntoConstraints = NO;
Now you have a clear slate and you can start setting your own constraints.
Step 2: Constraining the scrollView
First, let's create, as you did, the views references dictionary for AutoLayout to use:
NSDictionary *views = NSDictionaryOfVariableBindings(scrollView, singlePost, titleLabel, subtitleLabel, contentLabel);
Then, also as you already did, let's constrain the scroll view to be the size of its superview:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[scrollView]-0-|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[scrollView]-0-|" options:0 metrics:0 views:views]];
Step 3: Constraining the SinglePostView to push the scrollView's contentSize
For this step to be clear, you have to understand that every constraints set between a UIScrollView and its subviews will actually change the contentSize of the UIScrollView, not its actual bounds. For Example, if you constrain a UIImageView to the borders of its parent UIScrollView and then put an image twice the size of the UIScrollView inside the UIImageView, your image won't get shrunk, its the UIImageView that will take the size of its image and become scrollable inside the UIScrollView.
So here is what you have to set here:
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[singlePost]-0-|" options:0 metrics:0 views:views]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[singlePost]-0-|" options:0 metrics:0 views:views]];
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:singlePost
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:scrollView
attribute:NSLayoutAttributeWidth
multiplier:1.0f
constant:0.0f]];
First two constraints are pretty obvious. The third one, however, is here because, for your UILabels to display their content properly and still be readable, you will probably want them to be multilined and the scrolling to be vertical, not horizontal. That's why you set your SinglePostView's width to be the same as your scrollView's. This way, you prevent your scrollView's contentSize.width to be anything more than its bounds.width.
Step 4: Constraining your UILabels to "push" the bounds of your SinglePostView
Fourth and final step, you now need to set constraints on your SinglePostView's subviews, so that it gets an intrinsicContentSize from them.
Here is how you do it (simplest implementation, no margins, one label after the other vertically):
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[titleLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[subtitleLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[contentLabel]-0-|" options:0 metrics:0 views:views]];
[singlePost addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[titleLabel]-0-[subtitleLabel]-[contentLabel]-0-|" options:0 metrics:0 views:views]];
And with that you should be done.
One last advice, you should definitely look into UIStoryboard to do these kinds of things. It's a lot simpler and more visual.
Hope this helps,
P.S.: If you want, I can take some time and push a project using both UIStoryboard and the Visual Format Language on Github. Just tell me if you would need one.
Good luck.
in auto layout
frame of scrollview is decided by constraints between scrollview and superview of scrollview.
contentSize of scrollview is decided by constraints between scrollview and subview of scrollview.
you should set the size of singlePostView. ScrollView calculate contentSize from it. (you need to add size constraints explicitly)
CGFloat heightOfSinglePostView = calculate..
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[singlePost(heightOfSinglePostView)]|" options:0 metrics: 0 views:viewsDictionary]];

AutoLayout two labels within a TableViewCell?

I'm fairly new to iOS programming and probably don't understand the view hierarchy as well as I should and thus am failing to successfully get two labels within a custom table cell class I have created to autoresize properly. Namely the "translatesAutoresizingMaskIntoConstraints" property has me a little confused.
I am not using storyboards for this part of the code: I have a TableViewController where I create my own tableView in viewDidLoad. In cellForRowAtIndexPath I init my own TableViewCell implementation.
The problem I'm having is that when I set "setTranslatesAutoresizingMaskIntoConstraints" to NO for the table view and the UILabels I create then add my constraints, I get the following error:
"Terminating app due to uncaught exception `'NSInternalInconsistencyException',` reason: 'Auto Layout still required after executing `-layoutSubviews`. UITableView's implementation of `-layoutSubviews` needs to call super.'"
If I comment out the setTranslatesAutoresizingMaskIntoConstraints lines, my app runs however I get the following warning about the constraints:
"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)"
Essentially what I want to do is to enter code here have the two labels flush against each other and for them to resize based on orientation/device (I will be setting a background colour on them so want them to look 'continuous')
Can anyone help me out and explain what I am missing? Thanks in advance.
My code for adding the labels is:
self.nameLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 200.0f, 30.0f)];
self.nameLabel.textColor = [UIColor redColor];
self.nameLabel.font = [UIFont fontWithName:#"Helvetica Neue" size:12.0f];
self.nameLabel.backgroundColor = [UIColor brownColor];
[self.nameLabel setText:#"Test"];
// [self.nameLabel setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.contentView addSubview:self.nameLabel];
...
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"|-[nameLabel][summaryLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
I'm fairly new to ios programming and probably don't understand the
view hierarchy as well as I should and thus am failing to successfully
get two labels within a custom table cell class I have created to
autoresize properly. Namely the
"setTranslatesAutoresizingMaskIntoConstraints" property has me a
little confused.
Well, translatesAutoresizingMaskIntoConstraints is a property that is created by Apple to make the transition from Autoresizing (Spring and Struts) to Autolayout easier. Say, you had some AutoresizingMasks for your view and you just switched Autolayout ON without setting any constraints. Then your existing AutoresizingMasks will get converted into constraints which will hold the view in place. So, by default translatesAutoresizingMaskIntoConstraints property is set to be YES. However, when you start adding constraints, in 90% cases they will conflict with the constraints that got created by converting your AutoresizingMasks. So, it is better to turn it off by setting view.translatesAutoresizingMaskIntoConstraints = NO
In your code, the following might have been creating the problems:
The setting of Frame
You should not set frames to objects which you will be adding constraints to. It's a paradigm shift. When you think in Autolayout way, frames are but effects of setting right constraints who combinedly determine the frame of the view in question.
So, please remove the frame setting.
self.nameLabel = [[UILabel alloc] init]; will suffice.
Setting proper constraints
Your Code:
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints =
[NSLayoutConstraint constraintsWithVisualFormat:#"|-[nameLabel][summaryLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
NameLabel
Now, the above constraints tells us the nameLabel should be horizontally (as you have not mentioned H: or V:) spaced "standard" distance (20px) from container, adjacent to the summaryLabel.
But what about its Y position and Width and Height?
So we need more constraints.
summaryLabel
Same is applicable for summaryLabel.
So, lets define them properly:
NSDictionary *viewsDictionary =
NSDictionaryOfVariableBindings(nameLabel, summaryLabel);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[nameLabel(100)][summaryLabel]-|" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints1 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[nameLabel(30)]" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints2 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[summaryLabel]" options:0 metrics:nil views:viewsDictionary];
NSArray *constraints3 = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[nameLabel(==summaryLabel)]" options:0 metrics:nil views:viewsDictionary];
[self.view addConstraints:constraints];
[self.view addConstraints:constraints1];
[self.view addConstraints:constraints2];
[self.view addConstraints:constraints3];
Now your views will look fine.
At any point of time, to check if your views are missing any constraints, pause the debugger and type the following in the console
po [[UIWindow keyWindow] _autolayoutTrace]
it will show which of your views are having AMBIGUOUS LAYOUT
Also, remember in Storyboard/IB if the constraints are showing as "orange" colour, you need more constraints to define the objects position. Once you have added all necessary constraints, the constraints colours turn to "blue"
First, are you adding constraints to self.contentView after creation?
Second, maybe your constraint set is insufficient for autolayout and it creates own constraints based on autoresizing mask. Try to add vertical constraints and width constraints for labels.

UIScrollView doesn't work with Autolayout (iOS 6)

I made a few UIScrollView's in different views, they all worked without Autolayout.
I turned Autolayout on, because it was better for my app.
But since then, there's a big problem with my UIScrollView's:
No one is scrolling, they don't work.
Here's my code for a UIScrollView:
.m:
-(viewDidLoad) {
scrollerHome.contentSize = CGSizeMake(320, 1000);
scrollerHome.scrollEnabled = YES;
[self.view addSubview:scrollerHome];
scrollerHome.showsHorizontalScrollIndicator = false;
scrollerHome.showsVerticalScrollIndicator = false;
[super viewDidLoad];
}
.h:
#interface ViewController : UIViewController{
IBOutlet UIScrollView *scrollerHome;
}
Do I have to add some code because I turned on Autolayout?
You should call [super viewDidLoad] before doing anything !
In autolayout, you do not set the contentSize manually. Autolayout works slightly differently with scrollviews, whereby the contentSize of the scroll view is dictated by the constraints of the scrollview's subviews.
If you're trying to force the contentSize to some large size (for example, you're implementing some infinite scroller), you can just add a subview of the appropriate size, e.g.:
UIView *containerView = [[UIView alloc] init];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:containerView];
NSDictionary *views = NSDictionaryOfVariableBindings(containerView);
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[containerView]|" options:0 metrics:nil views:views]];
[self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[containerView(1000)]|" options:0 metrics:nil views:views]];
But if you were trying to set the contentSize in anticipation of adding subviews, you generally don't have to do anything, such as the above snippet. Just add your subviews, provide their constraints, and autolayout will adjust the scroll view's contentSize automatically.
As mentioned above, with autolayout, you can just add the subviews to your scrollview (with their constraints), and the contentSize will be calculated automatically for you.
There is a trick here, though. You sometimes you want to size a subview based upon the dimensions of the screen. But the usual technique of using the | symbols won't work. For example, for an imageview1 inside a scrollview, the usual #"H:|[imageview1]|" won't set the imageview1 to be the width of the screen, but rather it will define the scroll view's contentSize to match the width of imageview1, but it says nothing about what the width of that image view should be!
So, it's useful to capture a reference to the scroll view's superview. That way, you can use something like #"H:|[imageview1(==superview)]|", which not only says "make the scroll view's contentSize equal to the width of imageview1", but also "define the width of imageview1 to be equal to the width of the scroll view's superview."
Thus, for example, to add three images in a paging scroll view, you might do something like:
UIImageView *imageview1 = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"_DSC0004.jpg"]];
imageview1.contentMode = UIViewContentModeScaleAspectFit;
imageview1.translatesAutoresizingMaskIntoConstraints = NO;
[self.scrollView addSubview:imageview1];
UIImageView *imageview2 = ... // configured similar to imageview1
UIImageView *imageview3 = ... // configured similar to imageview1
UIView *superview = self.scrollView.superview;
NSDictionary *views = NSDictionaryOfVariableBindings(imageview1, imageview2, imageview3, superview);
// not only define the image view's relation with their immediate scroll view,
// but also explicitly set the size in relation to the superview, too!
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageview1(==superview)][imageview2(==superview)][imageview3(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview1(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview2(==superview)]|" options:0 metrics:nil views:views]];
[superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[imageview3(==superview)]|" options:0 metrics:nil views:views]];
self.scrollView.pagingEnabled = YES;
From the Apple iOS 6.0 release notes:
"In general, Auto Layout considers the top, left, bottom, and right edges of a view to be the visible edges. That is, if you pin a view to the left edge of its superview, you’re really pinning it to the minimum x-value of the superview’s bounds. Changing the bounds origin of the superview does not change the position of the view.
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom, and right edges within a scroll view now mean the edges of its content view."
You can find the full notes here and find the answer to your question in the section that I quoted from. They give code examples on how to use UIScrollView in a mixed Auto Layout environment.

AutoLayout constraints for a autoresizing view created in ViewController's loadView

My UIViewController creates its view by overwriting the loadView method:
- (void)loadView {
UIView *view = [[UIView alloc] init];
view.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
self.view = view;
}
Now I'd like to switch to AutoLayout and therefore add an
view.translatesAutoresizingMaskIntoConstraints = NO;
to the loadView method. Now I have to specify the same constraints which were autogenerated before. My approach was to overwrite updateViewConstraints with
- (void)updateViewConstraints {
if (0 == [[self.view constraints] count]) {
NSDictionary* views = #{#"view" : self.view};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:0 views:views]];
}
[super updateViewConstraints];
}
But I get an exception because I think this kind of constraints should go with the super view:
*** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal.
So, how do the correct Contraints have to look like?
You need to set the constraints on the superview. The exception is caused by referencing the superview by passing "|" in the visual format. If you update your code like the following it will work:
- (void)updateViewConstraints {
if (self.view.superview != nil && [[self.view.superview constraints] count] == 0) {
NSDictionary* views = #{#"view" : self.view};
[self.view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
[self.view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:0 views:views]];
}
[super updateViewConstraints];
}
In practice you'll probably want to check for something other than 0 constraints on the superview but this should help.
You don't have to set the constraints on your root view as Matt Neuburg explains the Chapter 19 of his Programming iOS 6 book, in section Manual Layout:
We have not bothered to give our view (self.view) a reasonable frame. This is because we are relying on someone else to frame the view appropriately. In this case, the “someone else” is the window, which responds to having its rootViewController property set to a view controller by framing the view controller’s view appropriately as the root view before putting it into the window as a subview.
The problem with CEarwood's approach is that this is a ViewController, and its view is not the subview of any other view, so calling self.view.subview just results in nil. Remember that the Apple documentation and guidelines strongly suggest that a UIViewController occupies more or less the whole screen (besides the navigation bar or tab bar etc.).
Palimondo's answer is basically the right one: your UIViewController needs to init its view in loadView, but it doesn't need to specify its frame or constraints because those are automatically set to the window's frame and constraints. This is exactly what is done by default if you don't implement loadView yourself.
I'm not sure you need to set the constraints for the root view of the window.
That said, your constraints look correct, I think the exception you get is because this:
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:0 views:views]];
uses the | notation to represent the view's superview. As the root level view, it has no superview. Something like this may work better:
- (void)loadView {
UIView *customView = [[UIView alloc] init];
[self.view addSubview:customView];
NSDictionary* views = #{#"customView" : customView};
[customView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[customView]|" options:0 metrics:0 views:views]];
[customView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[customView]|" options:0 metrics:0 views:views]];
}

Resources