Subview disappears with auto layout - objective-c

I just added a UIView as subview to the main UIView on interface builder (basic single view application).
Without setting any constraints, my subview disappears.
subview's frame = (0 0; 320 0);
Why is that?
If I try to add some constraints like for trailing space, leading space, top space and bottom space to be fixed, still my view disappears.
How can I solve this?
Thank you.
Just to clarify thing a little I created a test project (single view application), and added 2 subview to the main view like in the image. I didn't change any default constraint.
And you can see the error in the log of the image.
Logs:
**2013-01-19 17:16:02.435 Test[8871: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:0x106178c0 h=--- v=--- V:[UIWindow:0x9917040(480)]>",
"<NSLayoutConstraint:0x106159e0 UIView:0x991a5a0.bottom == UIWindow:0x9917040.bottom>",
"<NSLayoutConstraint:0x991ab00 V:|-(518)-[BottomView:0x9919c90] (Names: '|':UIView:0x991a5a0 )>",
"<NSLayoutConstraint:0x10615960 V:|-(20)-[UIView:0x991a5a0] (Names: '|':UIWindow:0x9917040 )>",
"<NSLayoutConstraint:0x991aa80 BottomView:0x9919c90.bottom == UIView:0x991a5a0.bottom>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x991aa80 BottomView:0x9919c90.bottom == UIView:0x991a5a0.bottom>
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.**
Constraints:
Also here is the result on the simulator:

It is good practice to understand those logs but if you are going to use Autolayout you are going to have to read up on this. Everyone says it is simply but I personally have not found it simple.
Apples Programming Guide For AutoLayout
Please read this guide especially the debugging section.
As a very very general rule if you are going to add a view then you need to turn off autoresizingmasks (Spring and struts) for the view. Add the view as a subview and give it 2 or 3 constraints. In your case above you would give it a constaint that it should have a left or leading space to superview of 0. A top space to superview of 0 and a width of 320.
EDIT; Here is an example of adding a view; note you do not need to create a frame. The constraints may be a little strange. The first puts the view in the centre of the superview. The second gives it a width of 200. The next method is the vertical constraint which puts the view at the bottom and makes it 2 high.
UIView *sView = [[UIView alloc] init];
[sView setTranslatesAutoresizingMaskIntoConstraints:NO];
[superView addSubview:sView];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:sView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:superView
attribute:NSLayoutAttributeCenterX
multiplier:1.0
constant:0]];
[superView addConstraint:[NSLayoutConstraint constraintWithItem:sView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:Nil
attribute:NSLayoutAttributeWidth
multiplier:1.0
constant:200]];
[superView addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:#"V:[sView(2)]|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(sView)]];

Related

Autolayout NSScrollview does not grow contentView

I have a scrollview that I'm trying to setup with auto layout.
The actual content is in a container view, this container view a set of visible box views. They are added without any problem to the container view. Each one is given a couple of constraints
NSViewController prev;
for(NSViewController *lvc in vcs) {
//each view is padded by 5 from left
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:lvc.view attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:prev.view attribute:NSLayoutAttributeTrailing multiplier:1 constant:5]];
//Constant height
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:lvc.view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:300]];
//Constant width
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:lvc.view attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:300]];
prev = lvc;
}
[_scrollView setDocumentView:containerView];
[_scrollView addConstraint:[NSLayoutConstraint constraintWithItem:listContainerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationGreaterThanOrEqual toItem:_scrollView attribute:NSLayoutAttributeWidth multiplier:1 constant:0]];
However the scroll view does not allow the container view to grow without growing the scrollview itself. The right edge of the containerView is attached to the right edge of the scroll view. If I grow the scroll view I can see there's more content from the list container view but only as big as the scroll view is.
The last row is my attempt to fix this by basically saying "let the container view grow larger than the scroll view". Isn't that the correct way of setting the constraint?
Add an NSStackView to your NIB and then select Editor > Embed In > Scroll View. Add constraints to the scroll view to keep it laid out relative to its superview and/or sibling views. Add constraints between the stack view and its superview (the clip view) to keep its top and leading edges the same as the clip view's. Add a constraint to keep the stack view's trailing edge greater than or equal to the clip view's trailing edge. Add a constraint to the keep the stack view's bottom edge greater than or equal to the clip view's bottom edge.
In your code, add the views of the view controllers in vcs as subviews of the stack view. The stack view's spacing will default to 8. In your code, you set spacing of 5. So, you can change the stack view's spacing to 5. (This can be done in the NIB.)

Force multiplier to update on Interface Builder constraint

I've created a height constraint for a UIView in the Interface Builder that sets the height to = the superview. I set the multiplier to 1/3 of the superview's width. This works great, it sets the height, say on iPhone 5/5S, to ~107pts.
For wider screens, I want to set the multiplier to 1/4 instead; I suppose I could do this with Size Classes, but I'm not sure that's the best way to go for this?
In my viewWillAppear method of a view controller, I've tried doing the following; no matter what I do though, it still sets the height to only ~107pts. Note that _controlsAspectRatio is an IBOutlet to the IB constraint.
_controlsAspectRatio = [NSLayoutConstraint constraintWithItem:_controlsAspectRatio.firstItem attribute:_controlsAspectRatio.firstAttribute relatedBy:_controlsAspectRatio.relation toItem:_controlsAspectRatio.secondItem attribute:_controlsAspectRatio.secondAttribute multiplier:1.0f/4.0f constant:0];
// I've tried several combinations of several layout refresh options, to no avail
[_controlsContainer setNeedsUpdateConstraints];
[_controlsContainer setNeedsLayout];
[_controlsContainer layoutIfNeeded];
NSLog(#"%0.4f - %0.4f", _controlsContainer.frame.size.height, _controlsAspectRatio.multiplier);
// Output: 106.6667 - 0
Had to remove the original and add a completely new constraint:
NSLayoutConstraint* newControlsAspectRatio = [NSLayoutConstraint constraintWithItem:_controlsAspectRatio.firstItem attribute:_controlsAspectRatio.firstAttribute relatedBy:_controlsAspectRatio.relation toItem:_controlsAspectRatio.secondItem attribute:_controlsAspectRatio.secondAttribute multiplier:1.0f/4.0f constant:0];
[self.view removeConstraint:_controlsAspectRatio];
[self.view addConstraint:newControlsAspectRatio];
[_controlsContainer layoutIfNeeded];
NSLog(#"%0.4f - %0.4f", _controlsContainer.frame.size.height, newControlsAspectRatio.multiplier);

UIScrollView breaks when using autolayout for size on iOS 8 / Xcode 6 GM Seed

How do I get a Scroll View to work using autolayout to calculate its content size?
It seems Xcode 6 will create a "UIView Encapsulated Layout Height" constraint whenever a Scroll View (with Autolayout) exists, and this constraint will forcibly set the height to the frame height (thus rendering the scroll functionality of a scroll view useless).
Error:
2014-09-09 21:06:01.059 ScrollViewLayoutBreaking[24488:88731] 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)
(
"<NSLayoutConstraint:0x7f9a19ebd150 V:[UIView:0x7f9a19ea7f90(530)]>",
"<NSLayoutConstraint:0x7f9a19ec1370 V:[UIView:0x7f9a19ebd5e0(110)]>",
"<NSLayoutConstraint:0x7f9a19ea9ee0 V:[_UILayoutGuide:0x7f9a19ec18d0]-(0)-[UIView:0x7f9a19ea7f90]>",
"<NSLayoutConstraint:0x7f9a19ec1fa0 V:[UIView:0x7f9a19ea7f90]-(0)-[UIView:0x7f9a19ebd5e0]>",
"<NSLayoutConstraint:0x7f9a19e8d270 V:[UIView:0x7f9a19ebd5e0]-(5)-[_UILayoutGuide:0x7f9a19ec2260]>",
"<_UILayoutSupportConstraint:0x7f9a19ea0ac0 V:[_UILayoutGuide:0x7f9a19ec18d0(0)]>",
"<_UILayoutSupportConstraint:0x7f9a19ea7380 V:|-(0)-[_UILayoutGuide:0x7f9a19ec18d0] (Names: '|':UIScrollView:0x7f9a19ea76a0 )>",
"<_UILayoutSupportConstraint:0x7f9a19e21d00 V:[_UILayoutGuide:0x7f9a19ec2260(0)]>",
"<_UILayoutSupportConstraint:0x7f9a19e8a930 _UILayoutGuide:0x7f9a19ec2260.bottom == UIScrollView:0x7f9a19ea76a0.bottom>",
"<NSLayoutConstraint:0x7f9a1c003f50 'UIView-Encapsulated-Layout-Height' V:[UIScrollView:0x7f9a19ea76a0(504)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7f9a19ebd150 V:[UIView:0x7f9a19ea7f90(530)]>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
Edit: All views inside this Scroll View are autolayouted and already pinned to both the Scroll View top and bottom, as well as both sides. I am aware that failing to bind it in all four sides will fail to create a content size. The autolayout and scrolling are correct in iOS 7 / XCode 5, but the exact same code and storyboard break in iOS 8 / XCode 6.
By your Screenshot, I'm guessing that you are using custom views in a scrollview so...
Your Content Views Are : STH,Foo,Bar,Baz,Qux,Blah,Woot,Lorem,Ipsum, Dolor
In code you can set the content size automatically with autolayout by setting all your content views related by each other, with the exception of the top and bottom view... these ones must be related to the edges of the scrollview, Here is an Example:
-(void)example{
UIView *parentView; //Can be the Scroll View or the Scroll View superview, it's better for the ScrollView to be the Owner of the restrictions
UIView *previousView;
UIScrollView *scrollView;
NSArray * arrayOfContentViews = #[STH,Foo,Bar,Baz,Qux,Blah,Woot,Lorem,Ipsum,Dolor];
for (UIView * currentView in arrayOfContentViews) {
//Important for Autolayout Restrictions
currentView.translateAutoresizingMask = NO;
[scrollview addSubview:currentView];
if (previousView == nil) {
//For the Top, Left and Right Attribute Edges of the ScrollView
previousView = scrollView;
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:currentView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:previousView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:0.0]];
//10% of the scrollview Frame Size (not Content Size)
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:currentView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:previousView
attribute:NSLayoutAttributeHeight
multiplier:0.1
constant:0.0]];
}else{
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:currentView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:previousView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:currentView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:previousView
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:0.0]];
}
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:currentView
attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:previousView
attribute:NSLayoutAttributeLeft
multiplier:1.0
constant:0.0]];
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:currentView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:previousView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0.0]];
previousView = currentView;
}
//Last View Dolor to the Bottom Edge of the scrollView
[parentView addConstraint:[NSLayoutConstraint constraintWithItem:previousView
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:scrollView
attribute:NSLayoutAttributeBottom
multiplier:1.0
constant:0.0]];
}
If you are using IB, then select the UIViewController and select remove all constraints from the AutoLayout menu at the bottom. Then select add missing constraints. This will solve your problem
For UIScrollView to work vertically it needs the chain of subviews from top to bottom.
Now if you set the top/bottom margin of its subviews you left no idea to scroll view how it should render it self vertically as it calculates its own height based on its content
So what you should do is allow scrollview to determine its own height independent of its children.
So add your scrollview in another view as subview with constraint related to that view and then you are free to add children relative to scrollview.
Figured I'd answer the question since it's receiving replies up to this day.
The code is autolayouted correctly. As stated in the question, the same code produced a perfect, no warning/errors Scroll View using Xcode 5 and iOS 7, and would break a layout constraint I did not manually create in Xcode 6 GM Seed and iOS 8.
It so seems it was an Xcode 6 GM Seed bug, the same code actually worked seamlessly in future updates of Xcode 6 after the first release, without any constraint breaking.

Autolayout: how to hide UIView containing subViews?

The best solution for hiding views with the new Autolayout is definitely to create height constraint for the view, connect it and create an outlet for it, and change self.myViewHeightConstriant.constant equals to 0. But suppose the view contains some other views, suppose an imageView and some label below it. Now, the imageView is 10px away from the top and has top space to superview constraint with 10px value. Trying to hide container UIView with constant = 0 shows an error in 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)
(
<NSLayoutConstraint:0xc7cedb0 V:[UIView:0xc7ce1e0(0)]>,
<NSLayoutConstraint:0xc7ceea0 V:[UIImageView:0xc7ce270]-(0)-| (Names: '|':UIView:0xc7ce1e0 )>,
<NSLayoutConstraint:0xc7cef30 V:|-(10)-[UIImageView:0xc7ce270] (Names: '|':UIView:0xc7ce1e0 )>
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0xc7ceea0 V:[UIImageView:0xc7ce270]-(0)-| (Names: '|':UIView:0xc7ce1e0 )>
The guess the problem is container UIView has height 0, but the imageView has top space offset 10px from it and Autolayout engine doesn't understand how to handle this situation. Tried to set clipSubviews for container view but that didn't help. Any ideas?
UPDATE several thoughts, creating an outlet topSpaceToSuperView constraint for the imageView and set its constaint also to 0 doesn't look very appealing. There should be more elegant solution than trashing the code with multiple outlets...
You can't go simple with container.hidden = YES?
Otherwise, it's the bottom constraint that's breaking things. #"V:|-10-[imageView]|" tells the container view that has to be at least 10 pts tall. But #"V:|-10-[imageView]" would be fine.
Perhaps instead of anchoring the imageView to the bottom of the container, setup a constraint for the imageView's height.
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[imageView]"
options:nil
metrics:nil
views:views];
[NSLayoutConstraint constraintWithItem:self.imageView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:self.containerView
attribute:NSLayoutAttributeHeight
multiplier:1.
constant:-10.f];
Update
You mention in the comments that the imageView isn't a predictable height. Since that's the case, it might be easier to just manage the container's height, but do it with separate constraints:
containerOpen = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-10-[imageView]|"
options:nil
metrics:nil
views:views];
containerClosed = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[containerView(0)]"
options:nil
metrics:nil
views:views];
// Toggle between the constraints to open close the container
- (void)toggleContainer
{
[self.containerView.superview removeConstraints:containerOpen];
[self.containerView.superview removeConstraints:containerClosed];
self.containerView.isOpen = !self.containerView.isOpen;
if (self.containerView.isOpen)
[self.containerView.superview addConstraints:containerOpen];
else
[self.containerView.superview addConstraints:containerClosed];
[self.containerView.superview setNeedsUpdateConstraints];
[self.containerView setNeedsLayout];
[self.containerView layoutIfNeeded];
}
If you have a leading, trailing, top, or bottom constraint you can set just set the relation to LessThanOrEqual when you hide it, and then back to equal when you show it.
Because relation is read-only, you'd do this by:
Outletting the constraint
Programmatically removing it from
the superview
Setting the outlet equal to a new constraint that
has the same parameters, except with your desired <= or = (depending
on if you are hiding or showing
Re-adding the constraint to the
superview
Essentially, all you're doing here is making that constraint small enough so that the height of your "big" view can == 0 when it's hidden.

Resize NSView to its subview using auto layout

In Interface Builder I've added a NSScrollView with a NSImageView, some labels, a horizontal separator and a NSView. The NSScrollView looks like the following:
Every element has constraints from the top left, and both the separator and the custom view also have a constraints to the right.
Sometimes I need to change the content of the custom view. I do this with the following code (options is the custom view, view is the view I want to display):
[view setTranslatesAutoresizingMaskIntoConstraints:NO];
options.subviews = [NSArray arrayWithObject:view];
// Fix the constraints.
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(view);
[options addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[view]|" options:0 metrics:nil views:viewsDictionary]];
[options addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[view]|" options:0 metrics:nil views:viewsDictionary]];
This works to some extent: the correct content is displayed in the view and the content's width is changed according to the constraints. However, the height of the options view doesn't change, so some content of the view view is not displayed. I tried to change this by manually setting the frame, but that doesn't work. Also, I tried the following to set a constraint:
[options addConstraint:[NSLayoutConstraint constraintWithItem:options attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:view attribute:NSLayoutAttributeHeight multiplier:1 constant:0]];
Nothing works though. Does anyone know how I can fix this?
A second problem I noticed is that even when the content of the NSScrollView is larger than the NSScrollView itself, scrolling doesn't work. No scrollbar is displayed. How can I fix that?
I still can't get my head around these harder problems with constraints...
I managed to solve my problem. It turned out I really had two problems:
I didn't set the correct constraints on the NSScrollView and its children;
I changed the content of the custom view, but didn't change its height.
My fix to both problems follows below.
NSScrollView constraints
After adding a NSScrollView in Interface Builder and placing some UI elements in the document view (the clip view's child), I had the following interface (the white part of the window is the NSScrollView, don't look at the constraints for now):
If you run this and resize the window, no scrollbars are displayed though. First of all we have to add top and leading constraints on the document view relative to its superview. Now we need to specify the size of the document view. This can be done in two ways, depending on your use-case:
Add constraints for the width and height of the document view. This way the same scrollbars are always displayed. The document view will then not change size depending on the content though, unless you automatically update these constraints;
Add constraints to every child of the document view, so that document view's width and height can be calculated. This was needed for my problem, and these constraints are displayed in the image above.
If you run this, everything works as expected. We still can't change the content of the custom view though, and that's a different problem.
Changing the custom view
Now we just need to change the content of the custom view. Since we set constraints on the custom view, these should also be changed. All of this is done with the following self-explanatory code (the custom view is named content):
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[content setAutoresizesSubviews:NO];
}
- (IBAction)button1:(id)sender {
[self showContent:view1];
}
- (IBAction)button2:(id)sender {
[self showContent:view2];
}
- (void)showContent:(NSView *)c {
// Remove all constraints.
[content removeConstraints:content.constraints];
// Add a height constraint.
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:content attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:c.frame.size.height];
[content addConstraint:constraint];
// Add a width constraint.
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:content attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:c.frame.size.width];
[content addConstraint:constraint];
[c setTranslatesAutoresizingMaskIntoConstraints:NO];
// Set the content.
content.subviews = [NSArray arrayWithObject:c];
// Add constraints from the content view to its child view.
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(c);
[content addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[c]|" options:0 metrics:nil views:viewsDictionary]];
[content addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[c]|" options:0 metrics:nil views:viewsDictionary]];
}
I hope this answer saves someone else the trouble of finding out about the strange behavior with NSScrollView and autolayout.