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

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)!

Related

autolayout - figuring out which constraints supersede which

I'm messing around with a really simple app to learn how to use AVFoundation (only been coding at all for about 14 weeks).
Included is a screenshot to help visualize my problem - my vertical constraints work just fine, and my horizontal constraints appear for two buttons that I have. However, my horizontal constraints (which I'm using to center a few objects) do not seem to work for the two labels I have underneath each button.
I wonder if the problem is that some constraints (perhaps the way I've created them) take priority over others and prevent some constraints from appearing properly? Really not sure here.
-(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);
//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]-150-[libraryButton]-[library]"
options:0
metrics:nil
views:views]];
[self.view addConstraints:constraints];
}
My guess is you wanted these views to appear centered. But that isn't what a "|-[view]-|" constraint will do. What you just told the view is, "I want you to fill the entire width of your superview, modulo the default padding". If you were to set a background color on your labels you'd see them extending the entire width of the view.
The easiest solution for you here is to set the text layout to center.

App crashing while adding constraints programatically

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

Changing programmatic/VFL constraints on-the-fly

I'm manually adding a subview to a view and am positioning it with constraints...
UIView *superview = self.view;
UIView *subview = self.subview;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(superview, subview);
[self.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]];
[self.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]];
This positions my subview neatly in the superview. However, at a later time I would like to apply a margin around my subview (with animation) so that it is inset by 100. So in effect, my constraints in visual format language would be...
"H:|-(100)-[subview]-(100)-|"
"V:|-(100)-[subview]-(100)-|"
How can I attach variables to my 'margin' value so that I can transition between the two types of display for the subview?
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(0)-[subview]-(0)-|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:viewsDictionary]
Will return an array with the constraints in the same order that they were specified, you can look for it inside the array and then change its constant value.
But I think the easiest way is to create a reference to it like so (you can store it in an internal variable or property):
NSLayoutConstraint* space = [NSLayoutConstraint constraintWithItem:self.button1 attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual toItem:self.button2
attribute:NSLayoutAttributeLeft multiplier:1.0 constant:12.0];
and add it like this:
[self.view addConstraints:#[space]];
then you can change space.constant to some value. and animate it like so:
[UIView animateWithDuration:1.0 animations:^{
// Make all constraint changes here
[self.view layoutIfNeeded];}];
Other approach would be to remove all constraints and add VFL constraints with updated values, and then perform layoutIfNeeded inside the animation block as above.

UIView programatic constraint height increase when keyboard present

I'm building a comment input control for an app. This control consists of a UITextView embedded in a UIView. All constraints are being handled programatically. What happens is when the user taps the UITextView, the keyboard will open. This calls the keyboard observer methods and I then adjust the bottom constraint for the comment input control to move up with the keyboard. However, I am also trying to increase the height of the input control at the same time so the user has more room to type. I'm having trouble achieving this.
-(void)updateViewConstraints
{
NSDictionary *views = #{
#"table" : self.commentsTableView,
#"seeMoreComments" : self.seeMoreCommentsView,
#"commentInput" : self.commentEntryInput
};
//See More Comments Constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[seeMoreComments]-0-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[seeMoreComments(45)]" options:0 metrics:nil views:views]];
//Table view constraints
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[table]-0-|" options:0 metrics:nil views:views]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[seeMoreComments]-0-[table]-0-|" options:0 metrics:nil views:views]];
//Comment entry input
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-0-[commentInput]-0-|" options:0 metrics:nil views:views]];
commentInputVerticalConstraint = [NSLayoutConstraint constraintWithItem:self.commentEntryInput
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeHeight
multiplier:1.0
constant:commentInputHeight];
if(commentInputBottomConstraint == nil)
{
commentInputBottomConstraint =
[NSLayoutConstraint constraintWithItem:self.commentEntryInput
attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeBottom multiplier:1.0
constant:0.0];
}
[self.view addConstraint:commentInputVerticalConstraint];
[self.view addConstraint:commentInputBottomConstraint];
[super updateViewConstraints];
}
Now I have a method that is called when keyBoardWillShow is called. This method animates the comment input control up when the keyboard appears.
(void)animateContentWithKeyboardInfo:(NSDictionary *)keyboardInfo
{
NSNumber *animationDuration = keyboardInfo[ UIKeyboardAnimationDurationUserInfoKey ];
NSValue *keyboardFrameValue = keyboardInfo[ UIKeyboardFrameEndUserInfoKey ];
CGRect keyboardFrame = [keyboardFrameValue CGRectValue];
UIViewAnimationCurve animationCurve = [keyboardInfo[ UIKeyboardAnimationCurveUserInfoKey ] intValue];
UIViewAnimationOptions animationOptions = animationOptionWithCurve(animationCurve);
commentInputBottomConstraint.constant = (keyboardFrame.origin.y - [UIScreen mainScreen].bounds.size.height);
//Increase the veritcal height of the comment input control
commentInputVerticalConstraint.constant = 125;
//Takes into account that the Tab Bar is 50 points, and adjust for this
//value.
if(keyboardAppeared == YES)
{
commentInputBottomConstraint.constant += TAB_BAR_OFFSET;
}
else
{
commentInputBottomConstraint.constant -= TAB_BAR_OFFSET;
}
[self.view layoutIfNeeded];
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:[animationDuration floatValue] delay:0.0 options:animationOptions animations:
^{
[self.view layoutIfNeeded];
} completion:nil];
}
However, when I try to adjust the constant of the of the commentInputVerticalConstraint I receive this error message:
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:0x1899fcb0 V:[CommentEntryInput:0x176e5160(50)]>",
"<NSLayoutConstraint:0x1899e5c0 V:[CommentEntryInput:0x176e5160(125)]>"
)
I'm not sure if there is a way for me to "reset" or adjust the constraint to handle when the keyboard appears and then put it back to normal when the keyboard disappears. Any help would be appreciated.
Your problem is that -(void)updateViewConstraints is getting called more than once. So you are creating a new constraint and adding it to the view twice. Try checking if the constraint is nil or not.
I also don't think you need the first [self.view layoutIfNeeded] before the animation change of the constant. When changing constant, just set it then wrap the [self.view layoutIfNeeded] in a animation block to animate to that new value.

NSLayoutConstraint, sub-viewing a UIView in a table view cell not working

So I am using a project that I found on Github — https://github.com/kmonaghan/CBPWordPress — and am trying to get a handle on it.
Essentially, what I want to do is create the floating "Cards" illusion that an app like Facebook has. The way that I have accomplished this in the past is by creating a subview UIView, rounding the corners, etc. before putting the title label, author name inside of that view. The issue is that I have typically done that with storyboards, while this example programmatically lays things out.
After toying around with it, I thought I had figured it out. This is how I set up the -(void)updateConstraints method:
(void)updateConstraints
{
if (!self.constraintsUpdated) {
self.contentView.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.frame), CBPLargePostPreviewTableViewCellHeight);
NSDictionary *views = #{#"postCommentLabel": self.postCommentLabel,
#"postDateLabel": self.postDateLabel,
#"postImageView": self.postImageView,
#"postTitleLabel": self.postTitleLabel,
#"roundedBackground": self.roundedBackground};
NSDictionary *metrics = #{#"padding": #(CBPPadding)};
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(4)-[roundedBackground]-(4)-|"
options:0
metrics:metrics
views:views]];
[self.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(4)-[roundedBackground]-(4)-|"
options:0
metrics:metrics
views:views]];
[self.roundedBackground addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-(5)-[postTitleLabel]-(5)-[postImageView(150)]-(5)-[postDateLabel]-(5)-|"
options:0
metrics:nil
views:views]];
[self.roundedBackground addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(padding)-[postTitleLabel]-(padding)-|"
options:0
metrics:metrics
views:views]];
[self.roundedBackground addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(padding)-[postImageView]-(padding)-|"
options:0
metrics:metrics
views:views]];
[self.roundedBackground addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(padding)-[postDateLabel]-(>=0)-[postCommentLabel]-(padding)-|"
options:0
metrics:metrics
views:views]];
[self.roundedBackground addConstraint:[NSLayoutConstraint constraintWithItem:self.postCommentLabel
attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.postDateLabel
attribute:NSLayoutAttributeCenterY
multiplier:1.0f
constant:0]];
self.constraintsUpdated = YES;
}
[super updateConstraints];
}
But upon running I get this error: [__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[4]'
I have tried creating bounds for the view, but to no avail. Does anybody have any suggestions? If there is a better approach to creating the effect I am looking for, I am also open to that.
It seems that you use roundedBackground in visual format strings, but have background
key in your views dict.

Resources