Space evenly across a view without margins - ios

I use following function to space a dynamic number of views across a View (with autolayout):
-(NSArray*)spaceViews:(NSArray *)views onAxis:(UILayoutConstraintAxis)axis
{
NSAssert([views count] > 1,#"Can only distribute 2 or more views");
NSLayoutAttribute attributeForView;
NSLayoutAttribute attributeToPin;
switch (axis) {
case UILayoutConstraintAxisHorizontal:
attributeForView = NSLayoutAttributeCenterX;
attributeToPin = NSLayoutAttributeRight;
break;
case UILayoutConstraintAxisVertical:
attributeForView = NSLayoutAttributeCenterY;
attributeToPin = NSLayoutAttributeBottom;
break;
default:
return #[];
}
CGFloat fractionPerView = 1.0 / (CGFloat)([views count] + 1);
NSMutableArray *constraints = [NSMutableArray array];
[views enumerateObjectsUsingBlock:^(UIView *view, NSUInteger idx, BOOL *stop)
{
CGFloat multiplier = fractionPerView * (idx + 1.0);
[constraints addObject:[NSLayoutConstraint constraintWithItem:view
attribute:attributeForView
relatedBy:NSLayoutRelationEqual
toItem:self
attribute:attributeToPin
multiplier:multiplier
constant:0.0]];
}];
[self addConstraints:constraints];
return [constraints copy];
}
(The function is from UIView-Autolayout)
The problem is that it has margins. How could this method be changed in order to evenly space the views without margins?
UPDATE
It seems that the method is not working correctly after all, the margins on the outer items are much higher than at the rest.
As an example I changed the code for the left arm to:
self.leftArm = [UIView autoLayoutView];
self.leftArm.backgroundColor = [UIColor grayColor];
[self.view addSubview:self.leftArm];
[self.leftArm pinAttribute:NSLayoutAttributeRight toAttribute:NSLayoutAttributeLeft ofItem:self.body withConstant:-10.0];
[self.leftArm pinAttribute:NSLayoutAttributeTop toSameAttributeOfItem:self.body withConstant:10.0];
[self.leftArm constrainToSize:CGSizeMake(20.0, 200.0)];
Now the robot looks like this:
Note that at the left arm the views are NOT evenly distributed, the margin on top and at the bottom are much higher. I can reproduce the same behavior horizontally and with less items.
I have successfully removed the margins by aligning the outer views with the sides of the superview and aligning the rest within the space between. But now, due to this problem, I always have bigger spaces between the outer views and the views next to them as between the rest.
Does anyone know how to correct this?
UPDATE 2
I have created a fork and added my function to it, I also extended the robot example so you can see what I mean.
Please take a look.

It looks as though this code is from UIView-Autolayout. The latest version of the library has a new method which lets you stop the spacing from being added to edges:
-(NSArray*)spaceViews:(NSArray*)views
onAxis:(UILayoutConstraintAxis)axis
withSpacing:(CGFloat)spacing
alignmentOptions:(NSLayoutFormatOptions)options
flexibleFirstItem:(BOOL)flexibleFirstItem
applySpacingToEdges:(BOOL)spaceEdges;
Just call this method instead and pass NO to the last argument.

I have extended jrturton's UIView-Autolayout (based on this answer) and created a pull request.
It is now possible to distribute the views evenly without margins by calling the new function:
-(NSArray*)spaceViews:(NSArray *)views
onAxis:(UILayoutConstraintAxis)axis
withMargin:(BOOL)margin
and setting the margin parameter to NO.
You can already find that version of UIView-Autolayout here.
The brand new robot with evenly spaced views as teeth:

Related

setting constraints in code doesn't produce the expected result

i'm trying to set a constraint in code and don't get the expected result.
I have a UIView container with 3 buttons (sub views) and i'm trying to set one's leading space to be the average of the other two's leading spaces (so it'll be in the middle, horizontally).
The numbers i get seem to be right when compared to the numbers i see on the storyboard when i place the 3 buttons in the position i want.
I'm getting the leading space by their frame's x value (i've double checked that aligmentRectForFrame: gives the same results), and i average them.
I use:
NSLayoutConstraint *twitterConstraint = [NSLayoutConstraint constraintWithItem:middleButton attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:average];
[twitterConstraint setPriority:UILayoutPriorityRequired];
[self.view setTranslatesAutoresizingMaskIntoConstraints:NO];
[NSLayoutConstraint activateConstraints:#[twitterConstraint]];
the basic functionality works i.e. if i put a number instead of the average i see results. I'm getting unexpected results with the current code. specifically, i'm getting the "middle button" on the right hand side of the other 2 buttons.
help!
idan
Expanding on why #Ken Thomases answer in comments works: Auto Layout first goes from subview-up to collect information, and then goes superview-down to set frames. So this code is sampling values of its subview frames, but at the time it is executed (in updateConstraints or updateViewConstraints or somewhere else) those views' frames haven't yet been set to their auto-layout-approved values. Unpredictable results can happen.
Calling -layoutIfNeeded on this view forces the Auto Layout engine to do this for the subviews--actaully do the layout work. So then sampling those subviews can work.
Unfortunately in this method, problems are created both sampling the frames to get information and calling layoutIfNeeded, which duplicates the expensive layout operations. As noted, it requires the buttons to be all the same size. Regardless, it's probably fine for this use case.
The way to set items to be evenly spaced using the native Auto Layout system (and allowing different-sized items) is spacer views. It's inelegant, but necessary. It can be done manually in IB, and here's how to do it with Visual Format Language:
NSMutableDictionary *autoLayoutDict = [[NSMutableDictionary alloc] init];
NSDictionary *setDictionary = #{
#"leftLabel":self.leftLabel,
#"middleLabel":self.middleLabel,
#"rightLabel":self.rightLabel
};
[autoLayoutDict setValuesForKeysWithDictionary:setDictionary];
int numberOfSpacerViewsRequired = 2;
for (int i = 0; i < numberOfSpacerViewsRequired; i++) {
UIView *spacerViewX = [[UIView alloc] init];
spacerViewX.backgroundColor = [UIColor clearColor];
spacerViewX.userInteractionEnabled = NO;
spacerViewX.translatesAutoresizingMaskIntoConstraints = NO;
NSString *spacerViewKey = [NSString stringWithFormat:#"spacerView%i", i];
[autoLayoutDict setObject:spacerViewX forKey:spacerViewKey];
[self.view addSubview:spacerViewX];
}
NSArray *constraintsToAdd = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[leftLabel]-0-[spacerView0(spacerView1)]-0-[middleLabel]-0-[spacerView1(spacerView0)]-0-[rightLabel]"
options:NSLayoutFormatAlignAllCenterY
metrics:0
views:autoLayoutDict];
[self.view addConstraints:constraintsToAdd];

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

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

How do I hide controls and cause others to shift place in iOS?

I'm programming an app for iOS 7 in Xcode 5.
I have three text boxes at the top of my page, each right above the other. Let's call them topTextBox, middleTextBox, and bottomTextBox.
My intent is that depending on a particular condition, the topTextBox might not be visible (or present, really) when the view loads.
If topTextBox isn't present (or hidden, or whatever) I would like middleTextBox and bottomTextBox to be placed further up the page...as if topTextBox wasn't ever there in the first place (so middleTextBox is in the spot topTextBox used to be in, and bottomTextBox is in the spot middleTextBox was in).
I'm using storyboards with AutoLayout ON. I can't seem to figure out what to do with the constraints for each of the three textBoxes to make this work. Making middleTextBox and bottomTextBox sit higher on the page when topTextBox is hidden doesn't need to be dynamic--I make the decision to show or not show topTextBox in ViewDidLoad()--I just need to get them to show up in the right place depending on my conditions.
Questions:
a. Is making topTextBox.hidden = YES the right way to get topTextBox to not be shown? Or is there some way to make it not noticed by the view at all?
b. What do I do with these constraints on middleTextBox and bottomTextBox to move them up on the page in this condition?
c. Is there anything else I should know to get this to work that I'm not thinking of? Perhaps a better method?
Thanks.
Have you tried just hard coding the frame logic? This could be very effective. All you have to do is conditionally populate an array containing pointers to your text fields and then perform a little frame arithmetic on their y origins. For example, this could easily display all the fields:
NSArray *fields = #[topTextField, middleTextField, bottomTextField];
for (int i = 0 ; i < fields.count ; i ++) {
UITextField *field = fields[i];
[field setFrame:CGRectMake(20.0, 20.0 + (i * 50.0), 280.0, 34.0)];
}
Produces this:
Or at its most basic level, something like this could be used to check whether any of the text fields should/shouldn't be shown, and either hides them, or adjusts their frame accordingly.
BOOL shouldShowTop = YES;
BOOL shouldShowMiddle = NO;
BOOL shouldShowBottom = YES;
NSMutableArray *fields = [NSMutableArray new];
if (shouldShowTop) {
[fields addObject:topTextField];
}else{
[topTextField setHidden:YES];
}
if (shouldShowMiddle) {
[fields addObject:middleTextField];
}else{
[middleTextField setHidden:YES];
}
if (shouldShowBottom) {
[fields addObject:bottomTextField];
}else{
[bottomTextField setHidden:YES];
}
for (int i = 0 ; i < fields.count ; i ++) {
UITextField *field = fields[i];
[field setFrame:CGRectMake(20.0, 20.0 + (i * 50.0), 280.0, 34.0)];
}
Will produce this:
Setting the height constraint of topTextField to 0 will help you to achieve this.
provided all textfields are connected through vertical space constraint
Setting the height of topTextField so that it has zero height could also work for you.
CGRect frame = topTextField.frame;
frame.size.height =0;
topTextField.frame = frame;
This is a good choice should you want to animate its re-appearance later on.
As for constraints, constrain the top of the topTextField to the superview, and then the top of middleTextField and bottomTextField to the bottom of the textfield above.
Do not set a constraint for the height of topTextField, but do set width constraints. You'll need to set the height of topTextField in viewDidLoad:

Auto layout visual programming language for an array of views

Let's say I have an array of views, and I want to stack these views in a list. Now, if I know ahead of time how many views there are, I could write a constraint like this:
"V:|-[view0]-[view1]-[view2]-[view_n]"
However, how can I accomplish something like this with a variable number of views in my array?
You can iterate over the array and build the string (use an NSMutableString). You need to add the views to a dictionary with keys that match the names you use in the format string.
Check out this awesome category:
https://github.com/jrturton/UIView-Autolayout
It has a spaceViews method that you can call on the container view and will space an array of views evenly along the specified axis.
There's some sample code in the demo project that should cover everything.
Here is how you would space some views evenly over the vertical axis:
This centre 4 views on the x axis and constrain the width to 150 points. The height would then be calculated depending on the height of self.view
#import "UIView+AutoLayout.h"
...
- (void)spaceViews
{
NSArray *views = #[ [self spacedView], [self spacedView], [self spacedView], [self spacedView] ];
[self.view spaceViews:views onAxis:UILayoutConstraintAxisVertical withSpacing:10 alignmentOptions:0];
}
- (UIView *)spacedView
{
//Create an autolayout view
UIView *view = [UIView autoLayoutView];
//Set the backgroundColor
[view setBackgroundColor:[UIColor redColor]];
//Add the view to the superview
[self.view addSubview:view];
//Constrain the width and center on the x axis
[view constrainToSize:CGSizeMake(150, 0)];
[view centerInContainerOnAxis:NSLayoutAttributeCenterX];
//Return the view
return view;
}
I had a requirement to add views from an array to a scrollview for my tutorial pages, in this case I built the VFL string by looping through the views, below is a snapshot, this code is to fit subview fully into scrollview's page. With some tweaks, padding,etc can be added. Anyways posting it here so that it helps someone.
Full code arrayAutolayout
/*!
Create an array of views that we need to load
#param nil
#result creates array of views and adds it to scrollview
*/
-(void)setUpViews
{
[viewsDict setObject:contentScrollView forKey:#"parent"];
int count = 20;//Lets layout 20 views
for (int i=0; i<=count; i++) {
// I am loading the view from xib.
ContentView *contenView = [[NSBundle mainBundle] loadNibNamed:#"ContentView" owner:self options:nil][0];
contenView.translatesAutoresizingMaskIntoConstraints = NO;
// Layout the text and color
[contenView layoutTheLabel];
[contentScrollView addSubview:contenView];
[viewsArray addObject:contenView];
}
}
/*!
Method to layout the childviews in the scrollview.
#param nil
#result layout the child views
*/
-(void)layoutViews
{
NSMutableString *horizontalString = [NSMutableString string];
// Keep the start of the horizontal constraint
[horizontalString appendString:#"H:|"];
for (int i=0; i<viewsArray.count; i++) {
// Here I am providing the index of the array as the view name key in the dictionary
[viewsDict setObject:viewsArray[i] forKey:[NSString stringWithFormat:#"v%d",i]];
// Since we are having only one view vertically, then we need to add the constraint now itself. Since we need to have fullscreen, we are giving height equal to the superview.
NSString *verticalString = [NSString stringWithFormat:#"V:|[%#(==parent)]|", [NSString stringWithFormat:#"v%d",i]];
// add the constraint
[contentScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:verticalString options:0 metrics:nil views:viewsDict]];
// Since we need to horizontally arrange, we construct a string, with all the views in array looped and here also we have fullwidth of superview.
[horizontalString appendString:[NSString stringWithFormat:#"[%#(==parent)]", [NSString stringWithFormat:#"v%d",i]]];
}
// Close the string with the parent
[horizontalString appendString:#"|"];
// apply the constraint
[contentScrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:horizontalString options:0 metrics:nil views:viewsDict]];
}
Below is the string created
H:|[v0(==parent)][v1(==parent)][v2(==parent)][v3(==parent)][v4(==parent)][v5(==parent)][v6(==parent)][v7(==parent)][v8(==parent)][v9(==parent)][v10(==parent)][v11(==parent)][v12(==parent)][v13(==parent)][v14(==parent)][v15(==parent)][v16(==parent)][v17(==parent)][v18(==parent)][v19(==parent)][v20(==parent)]|

AutoLayout: removeFromSuperview / removeConstraints throws exception and crashes hard

We use auto layout constraints selectively, primarily to position labels in relation to editable field elements (UITextView, UITextField, typically). However, since implementing auto layout for these fields, we're seeing a nasty exception and crash whenever we're unloading views, deallocating, etc. The exceptions are happening as it's attempting to remove the constraints from a view before unloading it.
Our view/controller hierarchy is as such:
UITableViewController (plain style, but with cell appearance to mimic grouped style)
--> UITableViewCell
----> UIViewController (container for editable form)
------> UICollectionViewController (editable form)
--------> UICollectionViewCell
-----------> UIViewController (editable field)
--------------> UILabel (field label) **HAS CONSTRAINTS**
--------------> UITextView / UITextField (field value) **HAS CONSTRAINTS**
Many times when the upper level table cells are being deallocated/replaced/reloaded, we see a huge exception and then crash as it's trying to deallocate/unload the view hierarchy within.
I've attempted to mitigate the crash by catching the exception (no help) and also by forcefully removing all of the constraints on the affected view and all of the subviews prior to deallocation/unload (in viewWillDisappear:) and it doesn't seem to help. I've even tried to remove these constraints one by one to see if there's one in particular that's causing the trouble but all of them are blowing up when we call removeConstraint: or removeConstraints: on a container in preparation for disappearing.
I'm baffled! Here's a snippet of our exception -- roughly about 3000 lines have been chopped out of it, so if you need more, just ask.
Exception while deallocating view: { Rows:
0x18911270.posErrorMarker == 4 + 1*0x18911270.negError + 1*0x189112f0.marker + -1*0x189113f0.negError + 1*0x189113f0.posErrorMarker + 1*0x18911a60.marker + -0.5*0x1892dae0.negError + 0.5*0x1892dae0.posErrorMarker + 1*0x18951520.negError + -1*0x18951520.posErrorMarker + -0.5*0x18958090.negError + 0.5*0x18958090.posErrorMarker
0x189112b0.negError == 12 + 1*0x189112b0.posErrorMarker + -1*0x189112f0.marker + 1*0x189113f0.negError + -1*0x189113f0.posErrorMarker + -1*0x18911a60.marker + 1*0x18925530.marker + 0.5*0x1892dae0.negError + -0.5*0x1892dae0.posErrorMarker + 1*0x1893e080.marker + 0.5*0x18958090.negError + -0.5*0x18958090.posErrorMarker + 1*0x18963640.marker
0x18911370.negError == 9 + -1*0x189112f0.marker + 1*0x18911370.posErrorMarker + 1*0x18925530.marker + 1*0x1892dae0.negError + -1*0x1892dae0.posErrorMarker + 1*0x1893e080.marker + 1*0x18963640.marker
0x189113b0.slackMarker == 2 + -1*0x189107d0.marker + 1*0x18910b90.negError + -1*0x18910b90.posErrorMarker +
........ EXPLETIVES DELETED .........
UITableView:0xca2b000.contentHeight == 36 + 1*0xc221c00.marker
UITableView:0xca2b000.contentWidth == 704 + 1*0xc239470.marker
UITableView:0xca2b000.minX == 0 + 1*0xc2a23f0.marker + -0.5*0xc2a2590.marker
UITableView:0xca2b000.minY == 0 + 1*0xc2a25d0.marker + -0.5*0xc2a2630.marker
UITableViewCellContentView:0x18ab13d0.Height == 174 + 1*0x18abd4f0.marker
UITableViewCellContentView:0x18ab13d0.Width == 704 + 1*0x18abd470.marker
........ EXPLETIVES DELETED .........
<NSAutoresizingMaskLayoutConstraint:0x18988bc0 h=-&- v=-&- UIView:0x18911e50.midY == UIView:0x1892d0c0.midY> Marker:0x18988bc0.marker
<NSAutoresizingMaskLayoutConstraint:0x18994b40 h=-&- v=-&- UIView:0xc4a6fb0.midX == UIView:0xc4b4990.midX> Marker:0x18994b40.marker
<NSAutoresizingMaskLayoutConstraint:0x18998480 h=-&- v=-&- UIView:0x18915180.width == UIView:0xc4c5970.width> Marker:0x18998480.marker
<NSAutoresizingMaskLayoutConstraint:0x18aae320 h=--& v=--& TapSectionalTableViewCell:0x18a3d270.midX == + 352> Marker:0x18aae320.marker
<NSAutoresizingMaskLayoutConstraint:0x18aae410 h=--& v=--& H:[TapSectionalTableViewCell:0x18a3d270(704)]> Marker:0x18aae410.marker
<NSAutoresizingMaskLayoutConstraint:0x18aae450 h=--& v=--& TapSectionalTableViewCell:0x18a3d270.midY == + 144> Marker:0x18aae450.marker
........ EXPLETIVES DELETED .........
<NSAutoresizingMaskLayoutConstraint:0xc2de2f0 h=--& v=--& TapGenericCollectionCell:0xc2ac500.midX == + 499> Marker:0xc2de2f0.marker
<NSAutoresizingMaskLayoutConstraint:0xc2de3b0 h=--& v=--& V:[TapGenericCollectionCell:0xc2ac500(34)]> Marker:0xc2de3b0.marker
<NSAutoresizingMaskLayoutConstraint:0xc2de430 h=-&- v=-&- UIView:0x18953f80.height == UIView:0xc2acb20.height> Marker:0xc2de430.marker
<NSAutoresizingMaskLayoutConstraint:0xc2de520 h=-&- v=-&- UIView:0x18923af0.height == UIView:0xc2ae570.height> Marker:0xc2de520.marker
<NSAutoresizingMaskLayoutConstraint:0xc2de560 h=--& v=--& H:[TapGenericCollectionCell:0xc2ac500(280)]> Marker:0xc2de560.marker
........ EXPLETIVES DELETED .........
<NSContentSizeLayoutConstraint:0xc2f5730 H:[_UIBaselineLayoutStrut:0x18994a30(0)] Hug:250 CompressionResistance:750> Marker:0xc2f5730.posErrorMarker
<NSContentSizeLayoutConstraint:0xc2f5730 H:[_UIBaselineLayoutStrut:0x18994a30(0)] Hug:250 CompressionResistance:750> Marker:0xc2f5730.posErrorMarker
<NSContentSizeLayoutConstraint:0xc2f5770 V:[_UIBaselineLayoutStrut:0x18994a30(18)] Hug:250 CompressionResistance:750> Marker:0xc2f5770.posErrorMarker
internal error. Cannot find an outgoing row head for incoming head UIView:0x189712b0.Width, which should never happen.'
/**** BEGIN Individual Field Controller - This code is from the base individual field controller used in our editable form collection *****/
- (void)viewDidLoad {
[super viewDidLoad];
self.view.clipsToBounds = YES;
self.view.opaque = YES;
CGRect viewFrame = self.view.frame;
viewFrame.size = [self defaultFieldSize];
self.view.frame = viewFrame;
if (self.backgroundColor) {
self.view.backgroundColor = self.backgroundColor;
}
else {
self.view.backgroundColor = [UIColor whiteColor];
}
[self createLabelAndField];
[self setLabelAndFieldContraints];
[self.view addConstraints:self.labelValueConstraints];
[self.view setNeedsUpdateConstraints];
}
- (void)createLabelAndField {
[self removeLabelAndField];
UILabel *label = [[UILabel alloc] init];
label.font = self.labelFont;
label.textColor = self.labelColor;
label.lineBreakMode = NSLineBreakByWordWrapping;
label.textAlignment = NSTextAlignmentLeft;
label.adjustsFontSizeToFitWidth = NO;
label.numberOfLines = 0;
if (self.backgroundColor) {
label.backgroundColor = self.backgroundColor;
}
else {
label.backgroundColor = [UIColor whiteColor];
}
[self.view addSubview:label];
self.label = label;
/// EXAMPLE valueView initialization from a subclass that handles long text
TapEditableTextView *textView = [[TapEditableTextView alloc] init];
if (self.hasLabelOverValue) {
textView.shouldMimicTextField = NO;
}
else {
textView.shouldMimicTextField = YES;
}
textView.delegate = self;
textView.keyboardType = UIKeyboardTypeDefault;
textView.font = self.valueFont;
textView.textColor = self.valueColor;
textView.textAlignment = NSTextAlignmentLeft;
textView.normalBackgroundColor = self.backgroundColor;
textView.editable = NO;
textView.textLines = self.textLines;
self.valueTextView = textView;
self.valueView = textView;
[self.view addSubview:textView];
}
- (void)removeLabelAndField {
[self clearConstraints];
if (self.label) {
[self.label removeFromSuperview];
self.label = nil;
}
if (self.valueView) {
[self.valueView removeFromSuperview];
self.valueView = nil;
}
}
- (void)clearConstraints {
if (self.isViewLoaded && self.labelValueConstraints) {
[self.view removeConstraints:self.labelValueConstraints];
}
self.labelValueConstraints = nil;
self.labelToValueHorizConstraint = nil;
self.valueWidthConstraint = nil;
}
// This is called in our field's viewDidLoad, after we've created our label and valueView (UITextField, UITextView, etc)
- (void)setLabelAndFieldContraints {
[self clearConstraints];
self.labelValueConstraints = [NSMutableArray array];
self.label.translatesAutoresizingMaskIntoConstraints = NO;
self.valueView.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *constraint = nil;
constraint = [NSLayoutConstraint
constraintWithItem:self.label attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeLeft
multiplier:1.0f constant:self.labelValueGap];
constraint.priority = UILayoutPriorityRequired;
[self.labelValueConstraints addObject:constraint];
constraint = [NSLayoutConstraint
constraintWithItem:self.label attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeTop
multiplier:1.0f constant:0];
constraint.priority = 550;
[self.labelValueConstraints addObject:constraint];
constraint = [NSLayoutConstraint
constraintWithItem:self.label attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeBottom
multiplier:1.0f constant:0];
constraint.priority = 400;
[self.labelValueConstraints addObject:constraint];
constraint = [NSLayoutConstraint
constraintWithItem:self.valueView attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeTop
multiplier:1.0f constant:0];
constraint.priority = UILayoutPriorityRequired;
[self.labelValueConstraints addObject:constraint];
constraint = [NSLayoutConstraint
constraintWithItem:self.valueView attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeBottom
multiplier:1.0f constant:0];
constraint.priority = 499;
[self.labelValueConstraints addObject:constraint];
constraint = [NSLayoutConstraint
constraintWithItem:self.valueView attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeRight
multiplier:1.0f constant: -(kDisclosureWidth + self.labelValueGap) ];
constraint.priority = 901;
[self.labelValueConstraints addObject:constraint];
constraint = [NSLayoutConstraint
constraintWithItem:self.valueView attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:self.label attribute:NSLayoutAttributeTrailing
multiplier:1.0f constant:self.labelValueGap];
constraint.priority = UILayoutPriorityDefaultHigh + 1;
[self.labelValueConstraints addObject:constraint];
self.labelToValueHorizConstraint = constraint;
constraint = [NSLayoutConstraint
constraintWithItem:self.label attribute:NSLayoutAttributeBaseline
relatedBy:NSLayoutRelationEqual
toItem:self.valueView attribute:NSLayoutAttributeBaseline
multiplier:1.0f constant:0.f];
constraint.priority = 600;
[self.labelValueConstraints addObject:constraint];
constraint = [NSLayoutConstraint
constraintWithItem:self.valueView attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.view attribute:NSLayoutAttributeWidth
multiplier:(1.f - self.labelWidthPercentage) constant:0];
constraint.priority = 305;
[self.labelValueConstraints addObject:constraint];
self.valueWidthConstraint = constraint;
[self setCompressionAndHuggingForLabelView:self.label];
[self setCompressionAndHuggingForValueView:self.valueView];
}
- (void)setCompressionAndHuggingForLabelView:(UILabel *)labelView {
if (!labelView) {
return;
}
[labelView setContentCompressionResistancePriority:510 forAxis:UILayoutConstraintAxisHorizontal];
[labelView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
[labelView setContentHuggingPriority:450 forAxis:UILayoutConstraintAxisHorizontal];
[labelView setContentHuggingPriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
}
- (void)setCompressionAndHuggingForValueView:(UIView *)valueView {
if (!valueView) {
return;
}
[valueView setContentCompressionResistancePriority:509 forAxis:UILayoutConstraintAxisHorizontal];
[valueView setContentCompressionResistancePriority:UILayoutPriorityDefaultHigh forAxis:UILayoutConstraintAxisVertical];
[valueView setContentHuggingPriority:300 forAxis:UILayoutConstraintAxisHorizontal];
[valueView setContentHuggingPriority:650 forAxis:UILayoutConstraintAxisVertical];
}
/****** END Individual Field Controller ******/
I had an (extensive) conversation with an Apple engineer about this crash.
Here are the two most probable causes:
You have an invalid constraint, such as view1.left = view2.left + 20 where view2 is unexpectedly nil, or has a multiplier of 0. Be sure to double (and triple) check your constraints to make sure that they are correct. Here are 2 examples of problematic constraints:
// The first constraint would be a problem if view2 were nil
[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeBottom multiplier:1 constant:20];
// The second constraint is a problem because the 0 multiplier causes view2 to be "lost"
[NSLayoutConstraint constraintWithItem:view1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:view2 attribute:NSLayoutAttributeBottom multiplier:0 constant:5];
You're hitting a bug in the internal Foundation auto layout engine related to accumulated loss of floating point precision. When you've crashed, the way you can know that this is the case is to search through the (large) exception log in the console for a very small (nearly zero) floating point number such as this:
<505:-7.45058e-08>*PWPlotLegendEntryView:0x600000582be0.Height{id: 34609} +
(Search for e- in the console output to find small numbers like this.) This number (-7.45058e-08 in this case) represents the coefficient at this particular point in time while the internal engine is solving constraints. In this case, the number is supposed to be exactly 0, but due to the way the auto layout engine does calculations with floating point numbers it has become an extremely tiny negative number instead which blows everything up. If you can find such a number in the output, you know that you've hit this bug.
How can you work around this issue?
Changing the order that you add (activate) constraints can end up changing the order of calculations in the internal engine, which as a result can cause this issue to disappear as the math is done without any problematic loss of precision.
This issue seems to come up more frequently when you have changed the content compression resistance or content hugging priorities for views, so try commenting out any code that does that to see if it's causing this bug to happen, or re-ordering it to happen earlier or later in your constraint setup code.
More details about my specific case:
I ran into this crash on iOS. The steps to reproduce it were quite interesting:
A view controller containing a table view was pushed on screen (in a navigation controller).
The table view had to contain enough cells so they didn't all fit in the visible area, then it had to be scrolled to the last cell and then back up a bit (presumably, this was causing cells to be reused, which was triggering this issue).
Then, when the view controller containing the table view was popped off the navigation stack, immediately after the pop animation completed the app would crash at the point where the view controller's view was removed from the view hierarchy.
After a lot of trial and error I was able to isolate the issue to one specific thing: setting the content compression resistance & hugging priorities for a UIImageView in each of the table view cells. In this case, the image view is being positioned using Auto Layout inside the cell, and to achieve the correct layout the image view needs to be exactly its intrinsic content size (the size of its image).
This was the problematic code:
// Inside of the UITableViewCell's updateConstraints method...
[self.imageView setContentCompressionResistancePriority:​UILayoutPriorityRequired forAxis:​UILayoutConstraintAxisHorizontal];
[self.imageView setContentCompressionResistancePriority:​UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
[self.imageView setContentHuggingPriority:​UILayoutPriorityRequired forAxis:​UILayoutConstraintAxisHorizontal];
[self.imageView setContentHuggingPriority:​UILayoutPriorityRequired forAxis:UILayoutConstraintAxisVertical];
Removing the above code and replacing it with 2 constraints (at Required priority) to fix the width & height of the image view to the image's size achieved the same result, but avoided the crash. Here's the replacement code (using PureLayout):
[self.imageView autoSetDimensionsToSize:self.imageView.image.size];
I also found that just moving the problematic 4 lines to a different place in my constraint setup code resolved the issue, presumably because this changed the order of calculations sufficiently to prevent the problematic loss of precision.
Deallocation problem — one possibility
Your code that works with auto-layout may well run on the main thread but one of the blocks that's run in background and uses your view (perhaps indirectly), may hold a strong reference the view or one of its owner like view controller (that's the default behavior of Objective-C blocks). When such a block is run and deallocated on a background queue, strong references that it captures are released on that same queue and you may face a well known deallocation problem.
In your view controller, make sure you are using weak reference to self in all blocks that do not need a strong reference (and may run in background). You can declare it like this: __weak typeof(self) weakSelf = self; before the block — and use weakSelf inside the block.
Same goes for any local variables that hold references to your views — make sure their values are captured as weak refs.
Another possibility
In my work, I've encountered a similar issue on iOS 6 when hidden view participated in layout. Removing the view from hierarchy (-[UIView removeFromSuperview]) instead of setting hidden property to YES fixed the issue for me.
Had the same issue, solved it by deleting constraints one at a time in IB until the crash was solved. This narrowed it down to the offending constraint. I then reinstated said constraint, but reversed the items:
You may be as lucky and be able to solve your AL problems as easily.
To make #smileyborg's awesome answer more actionable:
This can happen if you have any constraints with multipliers that might suffer from floating point precision issues.
To solve:
Go over all your constraints that have multipliers (in layout code or by manually editing a storyboard/nib and searching for multiplier=).
If multiplier isn't a "pretty" power of two float, turn it to the nearest one (you can use a floating point calculator)
To easy way to do 2 is to enter the number you value you want and the calculator and then switch off lower precision bits in the mantissa until the value matches the rounded decimal value at bottom of the calculator.
For anyone encountering this issue in any iOS version > 8.0, the Apple docs state to use the "active" property on the NSLayoutConstraint rather than the removeConstraint/addConstraint functions on UIView. Apple Docs addConstraint reference
I am getting this crash when I call removeConstraints: with a nil argument.
In my case it was a proportional width constraint with 8:9 multiplier. I changed it to 7:9 and it worked.
BTW the easiest way to find a constraint is to start removing views from view controller. Do it using binary algorithm :) removing half views, then half of the half that makes app crash, etc.
For me the problem was that I was removing a constraint at a time that suited me, after calling dequeueReusableCellWithReuseIdentifier while setting properties of my UICollectionViewCell. The solution was to instead call:
[_myUICollectionViewCell setNeedsUpdateConstraints];
and override:
-(void)updateConstraints
and do my messing there. Seems that you can't just remove constraints when you like.
I just stumbled across the same error under OSX Mavericks with an OSX app I'm developing, but unlike the other answers given, I definitely don't have any other threads interacting with the UI objects, and the view hierarchy in question is definitely visible too. I'm not using blocks either. Bizarrely the problem went away when I removed a vertical constraint on an NSTextField.
FWIW the problematic view whose removal from its superview causes the "internal error. Cannot find an outgoing row head for incoming head" error is one of many side-panel controls which together present the properties of objects in the main view which can be cut, copied, created etc. This means that the user can be pasting new objects into the main view quite rapidly, meaning the side-panel's controls are being destroyed and new ones created very rapidly, too. Of course, with everything in the main thread, this should not make a difference, but it seems to.
The exact constraint causing problems was
[self addConstraint:
[NSLayoutConstraint
constraintWithItem:control
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:other
attribute:NSLayoutAttributeHeight
multiplier:1.4
constant:0.0]];
where control is the (editable) NSTextField causing problems, and 'other' is another (non-editable) NSTextField label.
I got this problem with MZFormSheetController pod: https://github.com/m1entus/MZFormSheetController/issues/78
This code crashes:
[formSheetController.view addSubview:self.sharePanel];
// ...
[self.sharePanel removeFromSuperview]; // <-- CRASHES HERE
My solution is very strange but it works:
[self.sharePanel removeFromSuperview]; // <-- This line helps to avoid crash
[formSheetController.view addSubview:self.sharePanel];
// ...
[self.sharePanel removeFromSuperview];
And here is sharePanel property declaration:
#property (weak, nonatomic) IBOutlet UIView *sharePanel;
I got this crash when I still have a missing constraint in wAnyhAny mode, fixing this removed the error.
As the other answers in this thread indicate this is somehow an invalid autolayout/contraint problem, though it appears to be very finicky about what qualifies as "invalid".
Luckily I hadn't made many changes since my last commit and was able to track down the offending changes. For me having a view of 10 horizontal UIImageView with equal width and fixed 2:3 aspect ratio was the issue.
The crash only seemed to occur after leaving the UIViewController that contained this image row. Each UIImageView was set to UIViewContentModeScaleAspectFill. Removing this content mode change (which was done before the UIImages were set) seemed to fix my problem but wasn't an acceptable solution. I ended up removing the aspect ratio restriction and just using a fixed width and height for each image.
Why this was crashing my application I don't know... The crash could also ONLY be reproduce on an iPhone 4s running iOS 7.1.2. I tried to reproduce the same crash on an iPhone 4s simulator running iOS 9.1 without success. It also wouldn't crash when running on a phsyical iPhone 5 running iOS 9.1.
Hope that helps someone out there
According to Apple Documentation:
When developing for iOS 8.0 or later, set the constraint’s active property to YES instead of calling the addConstraint: method directly. The active property automatically adds and removes the constraint from the correct view.
In my case I had to modify the width constraint
for var constraint in self.navigationBar.constraints {
if constraint.identifier == "theProgressWidth" {
let sizeWidth = self.navigationBar.frame.size.width
constraint = NSLayoutConstraint(item: progress!, attribute: .Width, relatedBy: .Equal, toItem: self.navigationBar, attribute: .Width, multiplier: ((sizeWidth * (level / 100)) / sizeWidth), constant: 0)
constraint.active = true
}
}
I just spent the weekend trying to figure out the reason our app crashed with the debug console being filled with rows saying:
Failed to rebuild layout engine without detectable loss of precision. This should never happen. Performance and correctness may suffer.
This post led me in the right direciton, as the error turned out to be caused by having several constraints with multipliers set to arbitrary floating points with bad precision, as #yonix explained in his answer.
In our case the multipliers was dynamicaly set by the user draging a shape across the screen, ranging from 0 to 1.0, so naturally there would be cases where the multipliers required lots of bits set in the mantissa to be well defined.
By aligning the multipliers to a multiple of 1/2^n, the multipliers can be precisely defined with less bits in the mantissa, and the issue is averted.
We figured 9 bits (1/2^9) would be sufficient for us, and aligning the multiplier can be done by just multiplying by the factor, truncating and the dividing by the factor again.
let factor = 512.0 // 9-bits alignment
let alignedMultiplier = (multiplier * factor).rounded() / factor
Ensuring that all user defined multipliers are aligned before using them in our constraints, solved our problem.
So I add to this post for future reference, in case anyone else stumbles across the same issue.

Resources