UIView auto layout slide in screen or out of screen animation - ios

I can't figured it out how to do slide in/ out animation with auto layout.
I add constraints like this:
NSDictionary *viewsDic = #{#"tmpView" : tmpView};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[tmpView]|" options:0 metrics:nil views:viewsDic]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tmpView]|" options:0 metrics:nil views:viewsDic]];
[UIView animateWithDuration:.4 animations:^{
[self.view layoutIfNeeded];
}];
but this animates to grow from 0,0 position. how to achieve slide in/out animation?

As far as I understand, you need to get shift-effect. If my assumption is right, you need to manage horizontal or vertical padding manually during animation. And you need to get the reference on horizontal/vertical constraint:
#property (nonatomic, strong) NSLayoutConstraint *topPadding;
#property (nonatomic, strong) NSLayoutConstraint *heightConstraint;
Adding a constraint with initial value so that tmpView is out of visible part of its superview:
self.heightConstraint = [NSLayoutConstraint constraintWithItem:tmpView
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1.0
constant:0.0];
self.topPadding = [NSLayoutConstraint constraintWithItem:tmpView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:self.view.bounds.size.height];
[self.view addConstraint:self.topPadding];
[self.view addConstraint:self.heightConstraint];
Then we need simply to show tmpView with animation:
[self.view layoutIfNeeded];
[UIView animateWithDuration:.4 animations:^{
self.topPadding.constant = 0.0;
self.heightConstraint.constant = self.view.bounds.size.height;
[self.view layoutIfNeeded];
}];
Whit this example tmpView appears from the bottom.
Please ensure that self.topPadding won't conflict with other you constraints
To hide the view you can use something like this:
[self.view layoutIfNeeded];
[UIView animateWithDuration:.4 animations:^{
self.topPadding.constant = self.superview.bounds.size.height;
[self.view layoutIfNeeded];
}];
UPD.: Sometimes you also need to add hight constraints as well. Added use of self.heightConstraint in my example
UPD.: You still need horizontal constraints.
Please leave these lines:
NSDictionary *viewsDic = #{#"tmpView" : tmpView};
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[tmpView]|" options:0 metrics:nil views:viewsDic]];

Related

iOS - Performing several simultaneous animations with autolayout

I want to perform two simultaneous movement animations. First animation on firstView starts immediately. Second animation on secondView, starts after a slight delay while the first animation is still running. secondView constraint is related to firstView. The code works perfectly well on iOS 8.
firstView and secondView are subviews of view
view
|--- firstView
|--- secondView
Code:
UIView *firstView = ...;
UIView *secondView = ...;
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
[UIView animateWithDuration:0.5 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:secondView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:firstView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];
On iOS 7, once the second layoutIfNeeded is called, the first animations stops and only the seconds animation animates.
Any suggestions?
Answering my own question.
I ended up doing the animation in a 2-step (3-step, depends how you count) solution. First add the constraints without calling layoutIfNeeded. Then update the firtView and secondView frames. Lastly call layoutIfNeeded in the completion block of the last animation.
Code:
UIView *firstView = ...;
UIView *secondView = ...;
/* first step */
NSLayoutConstraint *constraint1 = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint1];
NSLayoutConstraint *constraint2 = [NSLayoutConstraint constraintWithItem:secondView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:firstView attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint2];
/* second step */
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect firstViewFrame = CGRectMake(...); // set frame according to constaint1
firstView.frame = firstViewFrame;
}];
[UIView animateWithDuration:0.5 delay:0.15 options:UIViewAnimationOptionCurveEaseInOut animations:^{
CGRect secondViewFrame = CGRectMake(...); // set frame according to constaint2
secondView.frame = secondViewFrame;
} completion:^(BOOL finished) {
/* second and a half step */
[self.view setNeedsLayout];
[self.view layoutIfNeeded];
}];
Two important notes:
You need to call layoutIfNeeded within the animation block. Apple
actually recommends you call it once before the animation block to
ensure that all pending layout operations have been completed.
You need to call it specifically on the parent view (e.g.
self.view), not the child view that has the constraints attached to
it. Doing so will update all constrained views, including animating
other views that might be constrained to the view that you changed
the constraint of (e.g. View B is attached to the bottom of View A
and you just changed View A's top offset and you want View B to
animate with it).
You must do it like this:
[self.view layoutIfNeeded];
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:firstView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0];
[self.view addConstraint:constraint];
[UIView animateWithDuration:0.5 delay:0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
[self.view layoutIfNeeded];
} completion:^(BOOL finished) {
}];

How to automatically change height of UILabel object and position of other elements below, based on the content of UILabel, with autolayout enabled

I've found some answers related to this topic, but nothing works for me. I've tried setPreferredMaxLayoutWidth:, setting number of lines to 0, setting height constraint
to XYZ, or equal or greater than XYZ... and all that in many different combinations. What could be possibly wrong? Any ideas?
Selected label is the one that needs to change the height based on content. Label below it, and possible other elements below should move down if the label has content that doesn't fit in 1 line. There are no constraint problems reported by IB.
Here's how I've just successfully done it:
I set numberOfLines on the label to 0, so it will grow and shrink as necessary.
I gave the label >= 0 left and right leading/trailing space constraints to the container margins, so it can grow to a maximum width.
I did not put any height constraint on the label. The height will therefore be determined by the content.
I made sure that no vertical constraints on anything below the label were limiting its downward growth.
In particular, bear in mind that if you set a constraint from anything to the bottom of the screen, you'll need to make sure that its priority (or the priority of another vertical constraint in the chain from the label to the bottom) is set to a lower priority than the vertical Content Compression Resistance Priority of the label. This will make sure that the growth of the label's content can overcome the other vertical constraints.
If you are using Auto Layout in code, setting the frame does not work. You have to create the constraint set required for the layout you want. For this case you would need to add height constraints for your UILabel.
Full tutorial on how to do is here : http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/
if you want to try to take care of it in code then you can approach it like this. i've laid out something akin to what you have then after 5 seconds it will swap in a new vertical constraint to make one of the labels taller. hope it steers you in the right direction ... or at least a direction!
NSArray * vertConstraint;
UIImageView * imageView = [[UIImageView alloc] init];
UILabel * labelOne = [[UILabel alloc] init];
UILabel * labelTwo = [[UILabel alloc] init];
UILabel * labelThree = [[UILabel alloc] init];
imageView.backgroundColor = [UIColor grayColor];
labelOne.backgroundColor = [UIColor redColor];
labelTwo.backgroundColor = [UIColor blueColor];
labelThree.backgroundColor = [UIColor orangeColor];
[imageView setTranslatesAutoresizingMaskIntoConstraints: NO];
[labelOne setTranslatesAutoresizingMaskIntoConstraints: NO];
[labelTwo setTranslatesAutoresizingMaskIntoConstraints: NO];
[labelThree setTranslatesAutoresizingMaskIntoConstraints: NO];
[self.view addSubview:imageView];
[self.view addSubview:labelOne];
[self.view addSubview:labelTwo];
[self.view addSubview:labelThree];
id topGuide = self.topLayoutGuide;
id bottomGuide = self.bottomLayoutGuide;
NSDictionary * viewsDictionary = NSDictionaryOfVariableBindings(imageView, labelOne,labelTwo,labelThree,topGuide, bottomGuide);
// initial vertical constraints. will be swapped out after 5 seconds (See below
vertConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[topGuide]-100-[imageView(==200)]-20-[labelOne(==20)]-20-[labelTwo(==20)]-20-[labelThree(==20)]-(>=5)-[bottomGuide]|" options:0 metrics: 0 views:viewsDictionary];
[self.view addConstraints:vertConstraint];
// horizontal constraints for all the elements
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[imageView(==200)]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[labelOne(==200)]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[labelTwo(==200)]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(>=0)-[labelThree(==200)]-(>=0)-|" options:0 metrics: 0 views:viewsDictionary]];
//additional constraints to center them
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:labelOne
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:labelTwo
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:labelThree
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1
constant:0]];
//delay 5 seconds then swap out vertical constraints
// in this case change the (==20) to (==40) for height of element
// you can edit that string more dynamically to fit your needs
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSArray * newVertConstraint = [NSLayoutConstraint constraintsWithVisualFormat:#"V:|[topGuide]-100-[imageView(==200)]-20-[labelOne(==20)]-20-[labelTwo(==40)]-20-[labelThree(==20)]-(>=5)-[bottomGuide]|" options:0 metrics: 0 views:viewsDictionary];
[self.view removeConstraints:vertConstraint];
[self.view addConstraints:newVertConstraint];
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:1.5 animations:^{
[self.view layoutIfNeeded];
}];
});

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.

Autolayout "conflicting constraints"

I've created a custom UIView that I use modally to display a UIToolbar and UIPickerView. I'm trying to make it very reusable, so I'm creating all of the UI in code, including setting constraints.
Here's the method body I use to set the view up to be added to another view, and then to animate the controls up onto the view from the bottom.
My view hierarchy is as follows:
"Owner" view (view to which this view is added):<br>
|-->"Background" view (set to the full size of "Owner", but mainly used as a dimmed background)<br>
|-->"Container" view (view which holds the toolbar and picker)
|--> Toolbar
|--> Picker
Here's the code I use to set up the UI:
- (void)prepareForView:(UIView *)view {
UIView *containerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
[containerView setTranslatesAutoresizingMaskIntoConstraints:NO];
self.containerView = containerView;
UIPickerView *picker = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 162.0f)];
[picker setTranslatesAutoresizingMaskIntoConstraints:NO];
self.picker = picker;
UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, 44.0f)];
[toolbar setTranslatesAutoresizingMaskIntoConstraints:NO];
UIBarButtonItem *done = ...;
UIBarButtonItem *flexSpace = ...;
UIBarButtonItem *cancel = ...;
toolbar.items = #[done, flexSpace, cancel];
[containerView addSubview:picker];
[containerView addSubview:toolbar];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[picker]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(picker)]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[toolbar]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(toolbar)]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[toolbar(==44)][picker(==162)]|" options:NSLayoutFormatAlignAllLeading metrics:nil views:NSDictionaryOfVariableBindings(toolbar, picker)]];
[containerView layoutIfNeeded];
[self addSubview:containerView];
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[containerView]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(containerView)]];
self.containerTop = [NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self attribute:NSLayoutAttributeTop multiplier:1.0f constant:self.frame.size.height];
[self addConstraint:self.containerTop];
[self layoutIfNeeded];
}
Basically, I want containerView to be sized according to its contents (which should be a static 206 points). Then, I set a vertical space constraint between its top and the top of its superview (which is self). Later, I animate changing that so that the toolbar and picker "slide up" onto the screen.
Here's the animation code (the error is always triggered BEFORE this point):
// Add the view as a subview
[view addSubview:self];
// Setup view for display (here's what triggers the message)
[self prepareForView:view];
// Animate into view
[UIView animateWithDuration:animated?0.4f:0.0f
animations:^{
self.alpha = 1.0f;
}
completion:^(BOOL finished) {
// Now, slide the container view in from the bottom of the screen
self.containerTop.constant = self.frame.size.height - self.containerView.frame.size.height;
[UIView animateWithDuration:animated?0.4f:0.0f
animations:^{
[self layoutIfNeeded];
}
completion:^(BOOL finished) {
if (postDisplay != nil) {
postDisplay();
}
}
];
}
];
This is currently displaying correctly on all simulators and devices in testing, but I hate having warnings/errors, and I'm worried that this might NOT work as desired at some point.
Here's the actual error message that's displayed (I'm pretty sure this is exactly the same error every time):
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:0x170283430 V:|-(0)-[UIToolbar:0x137590850] (Names: '|':UIView:0x170382b10 )>",
"<NSLayoutConstraint:0x170283480 V:[UIToolbar:0x137590850(44)]>",
"<NSLayoutConstraint:0x174084ba0 V:[UIToolbar:0x137590850]-(0)-[UIPickerView:0x137586100]>",
"<NSLayoutConstraint:0x174081fe0 V:[UIPickerView:0x137586100(162)]>",
"<NSLayoutConstraint:0x17409bbc0 V:[UIPickerView:0x137586100]-(0)-| (Names: '|':UIView:0x170382b10 )>",
"<NSLayoutConstraint:0x174095b80 V:[UIView:0x170382b10(736)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x174084ba0 V:[UIToolbar:0x137590850]-(0)-[UIPickerView:0x137586100]>
What I don't understand is that all of the constraints ARE expected, and then the displayed UI is what I intend. When I inspect the view's layout with po [self.containerView recursiveDescription] in the debugger, I see the frames being exactly what I think they ought to be. Where am I going wrong?
The "Container" view appears to have a height of 736 from the line below:
<NSLayoutConstraint:0x174095b80 V:[UIView:0x170382b10(736)]>
You have laid out the view as below:
------ Top of UIView ------- (y = 0)
- 0 Space -
UIToolBar (y = 0 to y = 44)
- 0 Space -
UIPickerView (y = 44 to y = 206) ***
- 0 Space -
------ Bottom of UIView ----- (y = 736)
*** This is where the conflict arrises. This can't be 0 space away from the bottom of the UIView with all of the other constraints set the way that they are or the height of the UIView cannot be what it is with all of the other constraints.
EDIT:
[self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[containerView]|" options:NSLayoutFormatAlignAllBaseline metrics:nil views:NSDictionaryOfVariableBindings(containerView)]];
When you use "|" within a the visualFormat for a constraint this is the parent view. Within this line you are saying you want the parent view to 0 space to the top of the [containerView] and also 0 space from the bottom of the [containerView]. This makes the container view the same height as the parent.
Edit #2:
Look at the following. I believe this is close to what you are trying to accomplish.
[containerView addSubview:picker];
[containerView addSubview:toolbar];
[self addSubview:containerView];
// Height & Width for containerView
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[containerView(%f)]", containerView.frame.size.height] options:nil metrics:nil views:#{#"containerView":containerView}]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[containerView(%f)]", containerView.frame.size.width] options:nil metrics:nil views:#{#"containerView":containerView}]];
// Height & Width for picker
[picker addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[picker(%f)]", picker.frame.size.height] options:nil metrics:nil views:#{#"picker":picker}]];
[picker addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[picker(%f)]", picker.frame.size.width] options:nil metrics:nil views:#{#"picker":picker}]];
// Height & Width for toolbar
[toolbar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"V:[toolbar(%f)]", toolbar.frame.size.height] options:nil metrics:nil views:#{#"toolbar":toolbar}]];
[toolbar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:#"H:[toolbar(%f)]", toolbar.frame.size.width] options:nil metrics:nil views:#{#"toolbar":toolbar}]];
// Vertical Positioning of picker & toolbar in containerView
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[picker]" options:nil metrics:nil views:#{#"picker":picker}]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[toolbar]-0-|" options:nil metrics:nil views:#{#"toolbar":toolbar}]];
// Horizontal Positioning of picker & toolbar in containerView
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:picker attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:toolbar attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
// Center containerView (X/Y) in parent (self)
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];
Edit #3: Using 'views' and 'metrics' dictionaries.
NSDictionary *views = #{#"containerView":containerView, #"picker":picker, #"toolbar":toolbar};
NSDictionary *metrics = #{#"hCV":containerView.frame.size.height,
#"wCV":containerView.frame.size.width,
#"hP":picker.frame.size.height,
#"wP":picker.frame.size.width,
#"hT":toolbar.frame.size.height,
#"wT":toolbar.frame.size.width};
[containerView addSubview:picker];
[containerView addSubview:toolbar];
[self addSubview:containerView];
// Height & Width for containerView
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[containerView(hCV)]" options:0 metrics:metrics views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[containerView(wCV)]" options:0 metrics:metrics views:views]];
// Height & Width for picker
[picker addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[picker(hP)]" options:0 metrics:metrics views:views]];
[picker addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[picker(wP)]" options:0 metrics:metrics views:views]];
// Height & Width for toolbar
[toolbar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[toolbar(hT)]" options:0 metrics:metrics views:views]];
[toolbar addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:[toolbar(wT)]" options:0 metrics:metrics views:views]];
// Vertical Positioning of picker & toolbar in containerView
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-0-[picker]" options:0 metrics:nil views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[toolbar]-0-|" options:0 metrics:nil views:views]];
// Horizontal Positioning of picker & toolbar in containerView
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:picker attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[containerView addConstraint:[NSLayoutConstraint constraintWithItem:containerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:toolbar attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
// Center containerView (X/Y) in parent (self)
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0.0]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:self attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:containerView attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0.0]];

How to resize custom UITableView separators on landscape and prevent from disappearing

I've decided to programmatically create my own UITableView separator lines because I need fine control over displaying a separator above and/or below each individual UITableViewCell. My tableView has static cells, so I do not create the separators in cellForRowAtIndexPath. Instead, I have propertys for each cell and in viewDidLoad, I add a top and/or bottom separator as needed. It's working, until I rotate to landscape and then the separator line does not stretch to fill the screen - it of course remains the same width it was when created. I'm not seeing how I can automatically adjust them to fit the width of the screen.
I tried adding Auto Layout constraints (leading, trailing, top/bottom), but for some reason it's not working - the width does not change, but there are no error messages logged to indicate anything is wrong with the constraints. The separator lines also sometimes disappear upon scroll or rotate, and if I comment out the auto layout constraints then they do not disappear.
So how can I make my custom cell separators always stretch to fill the device width upon rotation, and how do I prevent them from disappearing?
If it would be easier/better to create my custom cell separators in a different way, I am willing to do that. I just don't know how this can be done aside from my approach when the cells are static. I considered creating the views in the Storyboard, and setting up the constraints visually, but would that not be the equivalent of what I'm doing programmatically? If they were dynamic cells I would do it in cellForRowAtIndexPath.
//In viewDidLoad:
[self addTopSeparatorForCell:self.myCell];
//Helper method
- (void)addTopSeparatorForCell:(UITableViewCell *)cell {
UIView *topSeparator = [[UIView alloc] initWithFrame:CGRectMake(15, 1, cell.contentView.frame.size.width, 0.5)];
//add CALayer to preserve line separator visibility when row is highlighted
CALayer *backgroundColorLayer = [CALayer layer];
backgroundColorLayer.frame = topSeparator.bounds;
backgroundColorLayer.backgroundColor = [UIColor colorWithWhite:204/255.0f alpha:1].CGColor;
[topSeparator.layer addSublayer:backgroundColorLayer];
[cell.contentView addSubview:topSeparator];
//add auto layout constraints
topSeparator.translatesAutoresizingMaskIntoConstraints = NO;
NSLayoutConstraint *cn = nil;
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:15];
[cell.contentView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0];
[cell.contentView addConstraint:cn];
cn = [NSLayoutConstraint constraintWithItem:topSeparator
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:cell.contentView
attribute:NSLayoutAttributeTop
multiplier:1.0
constant:1];
[cell.contentView addConstraint:cn];
}
EDIT: Thanks to # user1966109, we've been able to solve the issue with the lines not extending to fill the width, and now they are preserved when highlighting a cell. But one issue still remains that I haven't been able to solve, since I'm not sure why it's occurring. The separator lines disappear after scrolling down the scrolling back up. It's related to the auto layout constraints though because a previous solution which had other issues did not exhibit this problem. Here's the current solution that causes the lines to disappear. I'd appreciate it if someone knows how to prevent this problem (and preserve the other issues already resolved).
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(15#750)-[myView]-(-47#750)-|" options:0 metrics:0 views:viewsDictionary]];
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[myView(2)]-(-2#750)-|" options:0 metrics:0 views:viewsDictionary]];
You should not mix initWithFrame and Auto Layout. You can have a good result with a few lines using Visual Format Language for Auto layout:
//#interface TableViewController ()
#property (weak, nonatomic) IBOutlet UITableViewCell *cell;
//#implementation TableViewController
- (void)viewDidLoad
{
[super viewDidLoad];
UIView *myView = [[UIView alloc] init];
myView.backgroundColor = [UIColor redColor];
[self.cell.contentView addSubview:myView];
myView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(myView);
[self.cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[myView]|" options:0 metrics:0 views:viewsDictionary]];
[self.cell.contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[myView(2)]" options:0 metrics:0 views:viewsDictionary]];
}
This handles rotation perfectly.
Edit!
Set the following constraints if using a accessory view:
//Set a negative value to the trailing space in order to display myView under the accessory view
//Those constraints work for both self.cell and self.cell.contentView (kind of odd)
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-(15#750)-[myView]-(-47#750)-|" options:0 metrics:0 views:viewsDictionary]];
[self.cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:[myView(2)]-(-2#750)-|" options:0 metrics:0 views:viewsDictionary]];
With the initial help of user1966109, I have figured out constraints that address all of the problems and are working well:
[cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeLeading
relatedBy:NSLayoutRelationEqual
toItem:cell
attribute:NSLayoutAttributeLeading
multiplier:1.0
constant:indent]];
[cell addConstraint:[NSLayoutConstraint constraintWithItem:imageView
attribute:NSLayoutAttributeTrailing
relatedBy:NSLayoutRelationEqual
toItem:cell
attribute:NSLayoutAttributeTrailing
multiplier:1.0
constant:0.0]];
[cell addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[imageView(0.5)]" options:0 metrics:0 views:viewsDictionary]];

Resources