I feel like it\'s a fairly common paradigm to show/hide UIViews
, most often UILabels
, depending on business logic. My question is, what is the best
There's a lot of solutions here but my usual approach is different again :)
Set up two sets of constraints similar to Jorge Arimany's and TMin's answer:
All three marked constraints have a the same value for the Constant. The constraints marked A1 and A2 have their Priority set to 500, while the constraint marked B has it's Priority set to 250 (or UILayoutProperty.defaultLow
in code).
Hook up constraint B to an IBOutlet. Then, when you hide the element, you just need to set the constraint priority to high (750):
constraintB.priority = .defaultHigh
But when the element is visible, set the priority back to low (250):
constraintB.priority = .defaultLow
The (admittedly minor) advantage to this approach over just changing isActive
for constraint B is that you still have a working constraint if the transient element gets removed from the view by other means.
The best practice is, once everything has the correct layout constraints, add a height or with constraint, depending how you want the surrounding views to move and connect the constraint into an IBOutlet
property.
Make sure that your properties are strong
in code yo just have to set the constant to 0 and activate it, tho hide the content, or deactivate it to show the content.
This is better than messing up with the constant value an saving-restoring it.
Do not forget to call layoutIfNeeded
afterwards.
If the content to be hidden is grouped, the best practice is to put all into a view and add the constraints to that view
@property (strong, nonatomic) IBOutlet UIView *myContainer;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *myContainerHeight; //should be strong!!
-(void) showContainer
{
self.myContainerHeight.active = NO;
self.myContainer.hidden = NO;
[self.view layoutIfNeeded];
}
-(void) hideContainer
{
self.myContainerHeight.active = YES;
self.myContainerHeight.constant = 0.0f;
self.myContainer.hidden = YES;
[self.view layoutIfNeeded];
}
Once you have your setup you can test it in IntefaceBuilder by setting your constraint to 0 and then back to the original value. Don't forget to check other constraints priorities so when hidden there is no conflict at all. other way to test it is to put it to 0 and set the priority to 0, but, you should not forget to restore it to the highest priority again.
I build category to update constraints easily:
[myView1 hideByHeight:YES];
Answer here: Hide autolayout UIView : How to get existing NSLayoutConstraint to update this one
Try BoxView, it makes dynamic layout concise and readable.
In your case it is:
boxView.optItems = [
firstLabel.boxed.useIf(isFirstLabelShown),
secondLabel.boxed.useIf(isSecondLabelShown),
button.boxed
]
I will provide my solution too, to offer variety .) I think creating an outlet for each item's width/height plus for the spacing is just ridiculous and blows up the code, the possible errors and number of complications.
My method removes all views (in my case UIImageView instances), selects which ones need to be added back, and in a loop, it adds back each and creates new constraints. It's actually really simple, please follow through. Here's my quick and dirty code to do it:
// remove all views
[self.twitterImageView removeFromSuperview];
[self.localQuestionImageView removeFromSuperview];
// self.recipients always has to be present
NSMutableArray *items;
items = [@[self.recipients] mutableCopy];
// optionally add the twitter image
if (self.question.sharedOnTwitter.boolValue) {
[items addObject:self.twitterImageView];
}
// optionally add the location image
if (self.question.isLocal) {
[items addObject:self.localQuestionImageView];
}
UIView *previousItem;
UIView *currentItem;
previousItem = items[0];
[self.contentView addSubview:previousItem];
// now loop through, add the items and the constraints
for (int i = 1; i < items.count; i++) {
previousItem = items[i - 1];
currentItem = items[i];
[self.contentView addSubview:currentItem];
[currentItem mas_remakeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(previousItem.mas_centerY);
make.right.equalTo(previousItem.mas_left).offset(-5);
}];
}
// here I just connect the left-most UILabel to the last UIView in the list, whichever that was
previousItem = items.lastObject;
[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) {
make.right.equalTo(previousItem.mas_left);
make.leading.equalTo(self.text.mas_leading);
make.centerY.equalTo(self.attachmentIndicator.mas_centerY);;
}];
I get clean, consistent layout and spacing. My code uses Masonry, I strongly recommend it: https://github.com/SnapKit/Masonry
I just found out that to get a UILabel to not take up space, you have to hide it AND set its text to an empty string. (iOS 9)
Knowing this fact/bug could help some people simplify their layouts, possibly even that of the original question, so I figured I'd post it.