App crashing while adding constraints programatically - ios

I am using visual format to define constraints. The goal is to place a UIView at the bottom of the super view say self.view with height fixed as 40, and width automatically. I have done this using Storyboard but I am unable to do it programatically. Here is the code what I have written. The app is crashing if i am not giving a fixed width. It is crashing with the following constraint: "H:[redView]|". If I change this to "H:[redView(100)]", it works. I don't want to use self.bounds and get width from there. It should stick from left side of super view, bottom and right side of the view.
Please help!
NSDictionary *viewsDictionary = #{#"redView":self.redView};
NSArray *constraint_H = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[redView(40)]"
options:0
metrics:nil
views:viewsDictionary];
NSArray *constraint_V = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[redView]|"
options:0
metrics:nil
views:viewsDictionary];
// 3. Define the redView Position
NSArray *constraint_POS_V = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[redView]|"
options:0
metrics:nil
views:viewsDictionary];
NSArray *constraint_POS_H = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[redView]"
options:0
metrics:nil
views:viewsDictionary];
[self.redView addConstraints:constraint_H];
[self.redView addConstraints:constraint_V];
[self.view addConstraints:constraint_POS_H];
[self.view addConstraints:constraint_POS_V];

You need to add redView to its super view first.
Also, the constraints are to be added to the superview, not redView.
[self.view addSubview:self.redView];
// create your constraints here
[self.view addConstraints:#[constraint_H, constraint_V, constraint_POS_V, constraint_POS_H]];
[self.view updateConstraintsIfNeeded];

In the visual constraints syntax the | refers to the parent view. The way you had that horizontal constraint before, adding it to the redview, it was interpreted as redview had a child, called the same, which obviously did not exist, so it crashed.
The vertical constraint works the way it is because it does not reference the parent view.
You could change your code a bit so that the constraints are more clear:
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:[redView(40)]|" options:0 metrics:nil
views:viewsDictionary];
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[redView]|" options:0 metrics:nil
views:viewsDictionary];
[self.view addConstraints:horizontalConstraints];
[self.view addConstraints:verticalConstraints];
Just remember you should always add the constraints for an UIView to it's superview.

Changing this line
[self.redView addConstraints:constraint_V];
with
[self.view addConstraints:constraint_V];
Solved myself. Thanks to me..:)

If you want to take the width of the view and apply it to red view, you only need to add a constraint like that:
NSArray *constraint_POS_H = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[redView]-|"
options:0
metrics:nil
views:viewsDictionary];

Related

how to avoid hardcoding with programmatic autolayout

I have an issue when using autolayout (I'm new to it) where, although my constraints function as expected (everything centered horizontally, vertical spacing as I want it), when I move to landscape orientation, the bottom button disappears.
I understand that this happens because I've constrained my objects based on a portrait orientation view, and this no longer applies when the height and width values shift as we move to landscape. I just don't really know how to account for these changes when changing orientation. Any advice?
code and screenshot below:
-(void)setConstraints {
[self.view removeConstraints:self.view.constraints];
UIButton *cameraButton = self.cameraButton;
UILabel *camera = self.videoLabel;
UIButton *libraryButton = self.libraryButton;
UILabel *library = self.libraryLabel;
NSDictionary *views = NSDictionaryOfVariableBindings(camera, cameraButton, libraryButton, library);
NSDictionary *metrics = #{#"horizontalSpacing":#500.0, #"verticalSpacing":#125};
//set up top button to be horizontally centered
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[cameraButton]-|"
options:0
metrics:nil
views:views];
//set up top button vertical from top of superview
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"V:|-175-[cameraButton]"
options:0
metrics:nil
views:views]];
//set up top button label to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"|-[camera]-|"
options:0
metrics:nil
views:views]];
//set up second button to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"|-[libraryButton]-|"
options:0
metrics:nil
views:views]];
//set up label for second button to be horizontally centered
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"|-[library]-|"
options:0
metrics:nil
views:views]];
//set up vertical constraints by spacing ALL objects appropriately
constraints = [constraints arrayByAddingObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: #"V:[cameraButton]-[camera]-verticalSpacing-[libraryButton]-[library]"
options:0
metrics:metrics
views:views]];
self.libraryLabel.textAlignment = NSTextAlignmentCenter;
self.videoLabel.textAlignment = NSTextAlignmentCenter;
self.libraryLabel.backgroundColor = [UIColor redColor];
[self.view addConstraints:constraints];
}
You are getting exactly what you asked for. If that isn't what you want, don't ask for that!
Think about your vertical constraints:
#"V:|-175-[cameraButton]"
#"V:[cameraButton]-[camera]-verticalSpacing-[libraryButton]-[library]"
That constitutes a single chain of constraints from the top down. Naturally, if the screen is shorter than your 175 plus your verticalSpacing plus the sizes and minimal spacings of the other views, the bottom view(s) will be off the bottom of the screen.
If that isn't what you want, change your design so that isn't what you get. For example, position some of your views from the top down and some of your views from the bottom up. Or allow some of your spaces to change as a percentage of the superview height.

Using NSLayoutConstraints to position a view outside the bounds of its superview

So I have a super view called self.content and I'm adding 2 subviews to it, namely self.bg1 and self.bg2 respectively. Initially I want self.bg2 to be sitting right outside the right bound of self.content. Users should be able to call in self.bg2 using a button. When the button is tapped self.bg1 moves to a position such that its right edge is touching the left edge of self.content. Here's the code that I've written, but it seems to only work when the user switches from self.bg1 to self.bg2(doesn't work backwards)
//BG1
self.bg1 = [[UIView alloc]init];
[self.bg1 setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.content addSubview:self.bg1];
//BG2
self.bg2 = [[UIView alloc]init];
[self.bg2 setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.content addSubview:self.bg2];
self.viewsDictionary = #{#"bg1":self.bg1,
#"bg2":self.bg2};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[bg1(%f)]",self.view.frame.size.width] options:0 metrics:nil views:self.viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[bg1]|" options:0 metrics:nil views:self.viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[bg2(%f)]",self.view.frame.size.width] options:0 metrics:nil views:self.viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[bg2]|" options:0 metrics:nil views:self.viewsDictionary]];
//self.bGConstraint is an NSArray
self.bGConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[bg1]-[bg2]" options:0 metrics:nil views:self.viewsDictionary];
[self.view addConstraints: self.bGConstraint];
And in the switchToBG1 method I have:
[self.view removeConstraint:[self.bGConstraint objectAtIndex:0]];
self.bGConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:|[bg1]-[bg2]" options:0 metrics:nil views:self.viewsDictionary];
[self.view addConstraints: self.bGConstraint];
Similarly in switchToBG2:
[self.view removeConstraint:[self.bGConstraint objectAtIndex:0]];
self.bGConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"H:[bg1]-[bg2]|" options:0 metrics:nil views:self.viewsDictionary];
[self.view addConstraints: self.bGConstraint];
Finally layoutIfNeeded in a UIView AnimationWithDuration...
*EDIT:
The animation when switching from BG1 to BG2 (i.e. left to right) is carried out successfully without any error, but when I try to go from right to left (BG2 back to BG1) the system throws me an Unable to simultaneously satisfy constraints. error message.
What am I doing wrong here? Thanks in advance!
What you are doing looks like the right idea, so there is probably just something wrong with your constraint-swapping logic, which causes something in your second constraint to conflict with something in your first set of constraints which are already there. To prevent that easily, structure it this way:
Up front, store all the constraints that position bg1 and bg2 for one position in one property, and all the constraints that position them for the other position in another property. Now when the time comes to swap, swap out all the old ones and swap in all the new ones. That way you will not confuse yourself so much, and you are guaranteed of consistency.
In other words, your code, which switches just one constraint, is trying to be too clever. It is better to keep it simple and stupid (and to have it work correctly)!

iOS constraints not updating as expected

I have a superview with circle view and a holderview that contains 3 labels as subview and is centred to the superview as seen in image
I have added constraints to the 3 labels with respect to holderview and also added constraints to holderview with respect to superview
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(titleLabel);
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[titleLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
[holderView addConstraints:constraints];
viewsDictionary = NSDictionaryOfVariableBindings(setLabel);
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[setLabel]-|"
options: 0
metrics:nil
views:viewsDictionary];
[holderView addConstraints:constraints];
viewsDictionary = NSDictionaryOfVariableBindings(repLabel);
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"|-[repLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
[holderView addConstraints:constraints];
viewsDictionary = NSDictionaryOfVariableBindings(titleLabel, setLabel, repLabel);
constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[titleLabel]-0-[setLabel]-0-[repLabel]-|"
options:0
metrics:nil
views:viewsDictionary];
[holderView addConstraints:constraints];
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_labelView);
NSArray *constraints =[NSLayoutConstraint constraintsWithVisualFormat:#"|-[_labelView]-|"
options:0
metrics:nil
views:viewsDictionary];
[self addConstraints:constraints];
There is a feature in app where the circle shrinks. I want the holderview and its subivews to shrink dynamically. Adding the constraints works for holderview but the subviews get misaligned.
To shrink i update the frame size of the holderview as the superview frame changes.
Can anyone point out the mistakes and guide me to proper solution ?
Using auto layout and changing frame property messes up things.
Create oultest to the constraints that you want to change or animate
__weak IBOutlet UIView *settingsView;
__weak IBOutlet NSLayoutConstraint *settingsBottomConstraint;
__weak IBOutlet NSLayoutConstraint *settingsViewHeightConstraint;
Update the constrains(Never the frame!)
settingsBottomConstraint.constant = - settingsViewHeightConstraint.constant;
[settingsView setNeedsUpdateConstraints];
[settingsView layoutIfNeeded];
isSettingsHidden = YES;
Recently I have worked with animation of views with autolayout and you can find your answer here
Auto Layout constraint change does not animate
You can also use the function updateConstraints.
[settingsView updateConstraints];

Resize subviews in Autolayout visual form

I've failed on migrating my app from Autoresize to Autolayout mechanism. All I need is a view with dynamic size and subviews that will be connected to hte left top corner with the same size as their superview. In IB I've set size for PieView (My superview) as 186x186. And let the IB to generate all needed Constraints (I will create new ones in code):
PieView.m -updateConstraints()
- (void)updateConstraints {
[super updateConstraints];
[self removeConstraintsAffectingViewAndSubviews];
[self setTranslatesAutoresizingMaskIntoConstraints:NO];
CGFloat sizeCoef = 0.7f;
CGFloat percent = [self.category getFillinPercent] / 100.0f;
percent = percent > 1.0f ? 1.0f : percent;
sizeCoef += (1.0f - sizeCoef) * percent;
NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f
constant:100.f];//kDiameter * sizeCoef];
NSLayoutConstraint *height = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0f
constant:100.0f];//kDiameter * sizeCoef];
//_imgEmptyCircle, _imgFullCircle - UIImageViews
NSDictionary *views = NSDictionaryOfVariableBindings(_imgEmptyCircle, _imgFullCircle, self);
NSDictionary *metrics = #{#"height":#100.0};
[self addConstraints:#[width, height]];
NSString *visualForm = #"H:|[_imgEmptyCircle(height)][_imgFullCircle(height)]|";
NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:visualForm
options:0
metrics:metrics
views:views];
visualForm = #"V:|[_imgEmptyCircle(height)][_imgFullCircle(height)]|";
NSArray *verticalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:visualForm
options:0
metrics:metrics
views:views];
[self addConstraints:verticalConstraints];
[self addConstraints:horizontalConstraints];
}
The result really surprised me. Blue one displayed correctly as I want, but green one has strange behavior.
Off course I have error: Unable to simultaneously satisfy constraints.
First imageView:
NSIBPrototypingLayoutConstraint:0xa2795d0 'IB auto generated at build time for view with fixed frame' V:[UIImageView:0x8a37150(186)]>
NSLayoutConstraint:0xa0305c0 V:[UIImageView:0x8a37150(100)]
Second imageView: the same one
Probable for PieView:
(
NSLayoutConstraint:0x8a40750 V:[CEPieView:0x8a36e50(100)],
NSLayoutConstraint:0xa030570 V:|-(0)-[UIImageView:0x8a37150] (Names: '|':CEPieView:0x8a36e50 ),
NSLayoutConstraint:0xa0305c0 V:[UIImageView:0x8a37150(100)],
NSLayoutConstraint:0xa0305f0 V:[UIImageView:0x8a37150]-(0)-[UIImageView:0xa277cf0]>
NSLayoutConstraint:0xa030620 V:[UIImageView:0xa277cf0(100)],
NSLayoutConstraint:0xa030650 V:[UIImageView:0xa277cf0]-(0)-| (Names: '|':CEPieView:0x8a36e50 )
)
Please, give me an advise how to fix it. How to create and old autoresizing mask(UIViewAutoresizingFlexibleHeight, UIViewAutoresizingFlexibleWidth) with the help of AutoLayout mechanism. Any help will be appreciated.
The way you're setting the constraints is wrong. Basically you're asking the superview to be 100pts by 100pts, and to contains two subviews that are each 100pts by 100pts and are beside AND on top of each other. So first your superview would have to be 200pts wide and/or tall, and then you can't have two view that are both beside each other and on top.
What you really want to do (I think) is having:
NSString *visualForm = #"H:|[_imgFullCircle]|"; // No need to explicitly set the height
Then add the corresponding constraint, then:
visualForm = #"H:|[_imgEmptyCircle(height)]|";
And add another constraint for this, and do the same for vertical constraints.
Basically you'd end up with:
//_imgEmptyCircle, _imgFullCircle - UIImageViews
NSDictionary *views = NSDictionaryOfVariableBindings(_imgEmptyCircle, _imgFullCircle, self);
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_imgEmptyCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[_imgFullCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imgEmptyCircle]|"
options:0
metrics:nil
views:views]];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[_imgFullCircle]|"
options:0
metrics:nil
views:views]];

NSLayoutConstraint setup

I'm trying to setup a panel to display some information. I'm having trouble getting the autolay out to work as i want.
here's what I'm trying to do:
I have a headerView, a UIImageView and 5 labels. I want the labels to line up vertically and i want them 8 spacing from the UIImageView. I can line them up vertically easily enough:
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[headerView]-[acresLabel]-[addedLabel(==acresLabel)]-[typeLabel(==acresLabel)]-[zonesLabel(==acresLabel)]-[sceneLabel(==acresLabel)]-|"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(headerView, acresLabel, addedLabel, typeLabel, zonesLabel, sceneLabel)]];
But getting them to line up off the image view is proving tedious/verbose:
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imageView]-[acresLabel]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(imageView, acresLabel)]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imageView]-[addedLabel]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(imageView, addedLabel)]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imageView]-[typeLabel]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(imageView, typeLabel)]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imageView]-[zonesLabel]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(imageView, zonesLabel)]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imageView]-[sceneLabel]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(imageView, sceneLabel)]];
Seems like a better way to do this then set up a constraint for each label to the image view. I tried adding NSLayoutFormatAlignAllCenterX to the first constrain, but it sets them all center to the content view's center X, doesn't line the label's center X up with each other. NSLayoutFormatAlignAllBaseline gives an error and crash when used vertically.
I'll have to do this again for the data (Acres: 'value').
You're on the right track with NSLayoutFormatAlignAllCenterX. All you need to do differently is isolate V:[headerView]-[acresLabel] from the rest of the constraint, since you don't want headerView's CenterX to be aligned with the 5 labels'.
// NSDictionary* views ;
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[headerView]-[acresLabel]" options:0 metrics:nil views:views] ;
// Use any of the horizontal NSLayoutFormats (NSLayoutFormatAlignAllLeft, NSLayoutFormatAlignAllRight, NSLayoutFormatAlignAllLeading, NSLayoutFormatAlignAllTrailing, NSLayoutFormatAlignAllCenterX)
[NSLayoutConstraint constraintsWithVisualFormat:#"V:[acresLabel]-[addedLabel(==acresLabel)]-[typeLabel(==acresLabel)]-[zonesLabel(==acresLabel)]-[sceneLabel(==acresLabel)]-|" options:NSLayoutFormatAlignAllCenterX metrics:0 views:views] ;
[NSLayoutConstraint constraintsWithVisualFormat:#"H:[imageView]-[acresLabel]" options:0 metrics:nil views:views] ;

Resources