问题
I have designed my custom Cell in IB, subclassed it and connected my outlets to my custom class. I have three subviews in cell content which are: UIView (cdView) and two labels (titleLabel and emailLabel). Depending on data available for each row, sometimes I want to have UIView and two labels displayed in my cell and sometimes only two labels. What I am trying to do is to set constraints that way if I set UIView property to hidden or I will remove it from superview the two labels will move to the left. I tried to set UIView leading constraint to Superview (Cell content) for 10px and UILabels leading Constraints for 10 px to the next view (UIView). Later in my code
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(IndexPath *)indexPath {
...
Record *record = [self.records objectAtIndex:indexPath.row];
if ([record.imageURL is equalToString:@""]) {
cell.cdView.hidden = YES;
}
I am hiding my cell.cdView and I would like the labels to move to the left however they are staying in the same position in Cell. I tried to remove cell.cdView from superview but it didn't work either. I have attached image to clarify what I am about.

I know how to do this programatically and I am not looking for that solution. What I want is to set constraints in IB and I expect that my subviews will move dynamically if other views are removed or hidden. Is it possible to do this in IB with auto-layout?
.....
回答1:
It is possible, but you'll have to do a little extra work. There are a couple conceptual things to get out of the way first:
- Hidden views, even though they don't draw, still participate in Auto Layout and usually retain their frames, leaving other related views in their places.
- When removing a view from its superview, all related constraints are also removed from that view hierarchy.
In your case, this likely means:
- If you set your left view to be hidden, the labels stay in place, since that left view is still taking up space (even though it's not visible).
- If you remove your left view, your labels will probably be left ambiguously constrained, since you no longer have constraints for your labels' left edges.
What you need to do is judiciously over-constrain your labels. Leave your existing constraints (10pts space to the other view) alone, but add another constraint: make your labels' left edges 10pts away from their superview's left edge with a non-required priority (the default high priority will probably work well).
Then, when you want them to move left, remove the left view altogether. The mandatory 10pt constraint to the left view will disappear along with the view it relates to, and you'll be left with just a high-priority constraint that the labels be 10pts away from their superview. On the next layout pass, this should cause them to expand left until they fill the width of the superview but for your spacing around the edges.
One important caveat: if you ever want your left view back in the picture, not only do you have to add it back into the view hierarchy, but you also have to reestablish all its constraints at the same time. This means you need a way to put your 10pt spacing constraint between the view and its labels back whenever that view is shown again.
回答2:
Adding or removing constraints during runtime is a heavyweight operation that can affect performance. However, there is a simpler alternative.
For the view you wish to hide, set up a width constraint. Constrain the other views with a leading horizontal gap to that view.
To hide, update the .constant
of the width constraint to 0.f. The other views will automatically move left to assume position.
See my other answer here for more details:
How to change label constraints during runtime?
回答3:
For those who support iOS 8+ only, there is a new boolean property active. It will help to enable only needed constraints dynamically
P.S. Constraint outlet must be strong, not weak
Example:
@IBOutlet weak var optionalView: UIView!
@IBOutlet var viewIsVisibleConstraint: NSLayoutConstraint!
@IBOutlet var viewIsHiddenConstraint: NSLayoutConstraint!
func showView() {
optionalView.isHidden = false
viewIsVisibleConstraint.isActive = true
viewIsHiddenConstraint.isActive = false
}
func hideView() {
optionalView.isHidden = true
viewIsVisibleConstraint.isActive = false
viewIsHiddenConstraint.isActive = true
}
Also to fix an error in storyboard you'll need to uncheck Installed
checkbox for one of these constraints.
UIStackView (iOS 9+)
One more option is to wrap your views in UIStackView
. Once view is hidden UIStackView
will update layout automatically
回答4:
UIStackView
repositions its views automatically when the hidden
property is changed on any of its subviews (iOS 9+).
UIView.animateWithDuration(1.0) { () -> Void in
self.mySubview.hidden = !self.mySubview.hidden
}
Jump to 11:48 in this WWDC video for a demo:
Mysteries of Auto Layout, Part 1
回答5:
My project uses a custom @IBDesignable
subclass of UILabel
(to ensure consistency in colour, font, insets etc.) and I have implemented something like the following:
override func intrinsicContentSize() -> CGSize {
if hidden {
return CGSizeZero
} else {
return super.intrinsicContentSize()
}
}
This allows the label subclass to take part in Auto Layout, but take no space when hidden.
回答6:
For the Googlers: building on Max's answer, to solve the padding issue that many have noticed I simply increased the height of the label and used that height as the separator instead of actual padding. This idea could be expanded for any scenario with containing views.
Here's a simple example:

In this case, I map the height of the Author label to an appropriate IBOutlet
:
@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
and when I set the height of the constraint to 0.0f
, we preserve the "padding", because the Play button's height allows for it.
回答7:
connect constraint between uiview and labels as IBOutlet and set priority member to a less value when set hidden = YES
回答8:
What I ended up doing was creating 2 xibs. One with the left view and one without it. I registered both in the controller and then decided which to use during cellForRowAtIndexPath.
They use the same UITableViewCell class. The downside is that there is some duplication of the content between the xibs, but these cells are pretty basic. The upside is that I don't have a bunch of code to manually manage removing view, updating constraints, etc.
In general, this is probably a better solution since they are technically different layouts and therefore should have different xibs.
[self.table registerNib:[UINib nibWithNibName:@"TrackCell" bundle:nil] forCellReuseIdentifier:@"TrackCell"];
[self.table registerNib:[UINib nibWithNibName:@"TrackCellNoImage" bundle:nil] forCellReuseIdentifier:@"TrackCellNoImage"];
TrackCell *cell = [tableView dequeueReusableCellWithIdentifier:(appDelegate.showImages ? @"TrackCell" : @"TrackCellNoImage") forIndexPath:indexPath];
回答9:
In this case, I map the height of the Author label to an appropriate IBOutlet:
@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
and when I set the height of the constraint to 0.0f, we preserve the "padding", because the Play button's height allows for it.
cell.authorLabelHeight.constant = 0;
回答10:
Use two UIStackView Horizontal and Vertical, when some subview view in stack is hidden other stack subviews will be moved, use Distribution -> Fill Proporionally for Vertical stack with two UILabels and need set width and height constaints for first UIView
回答11:
Try this,I have implemented below code ,
I have one View on ViewController in that added other three views, When any view is hidden other two view will move,Follow below steps. ,
1.ViewController.h File
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *viewOne;
@property (strong, nonatomic) IBOutlet UIView *viewTwo;
@property (strong, nonatomic) IBOutlet UIView *viewThree;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewOneWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewTwoWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewThreeWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewBottomWidth;
@end
2.ViewController.m
#import "ViewController.h"
@interface ViewController ()
{
CGFloat viewOneWidthConstant;
CGFloat viewTwoWidthConstant;
CGFloat viewThreeWidthConstant;
CGFloat viewBottomWidthConstant;
}
@end
@implementation ViewController
@synthesize viewOne, viewTwo, viewThree;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a
nib.
/*
0 0 0
0 0 1
0 1 0
0 1 1
1 0 0
1 0 1
1 1 0
1 1 1
*/
// [viewOne setHidden:NO];
// [viewTwo setHidden:NO];
// [viewThree setHidden:NO];
// [viewOne setHidden:NO];
// [viewTwo setHidden:NO];
// [viewThree setHidden:YES];
// [viewOne setHidden:NO];
// [viewTwo setHidden:YES];
// [viewThree setHidden:NO];
// [viewOne setHidden:NO];
// [viewTwo setHidden:YES];
// [viewThree setHidden:YES];
// [viewOne setHidden:YES];
// [viewTwo setHidden:NO];
// [viewThree setHidden:NO];
// [viewOne setHidden:YES];
// [viewTwo setHidden:NO];
// [viewThree setHidden:YES];
// [viewOne setHidden:YES];
// [viewTwo setHidden:YES];
// [viewThree setHidden:NO];
// [viewOne setHidden:YES];
// [viewTwo setHidden:YES];
// [viewThree setHidden:YES];
[self hideShowBottomBar];
}
- (void)hideShowBottomBar
{
BOOL isOne = !viewOne.isHidden;
BOOL isTwo = !viewTwo.isHidden;
BOOL isThree = !viewThree.isHidden;
viewOneWidthConstant = _viewOneWidth.constant;
viewTwoWidthConstant = _viewTwoWidth.constant;
viewThreeWidthConstant = _viewThreeWidth.constant;
viewBottomWidthConstant = _viewBottomWidth.constant;
if (isOne && isTwo && isThree) {
// 0 0 0
_viewOneWidth.constant = viewBottomWidthConstant / 3;
_viewTwoWidth.constant = viewBottomWidthConstant / 3;
_viewThreeWidth.constant = viewBottomWidthConstant / 3;
}
else if (isOne && isTwo && !isThree) {
// 0 0 1
_viewOneWidth.constant = viewBottomWidthConstant / 2;
_viewTwoWidth.constant = viewBottomWidthConstant / 2;
_viewThreeWidth.constant = 0;
}
else if (isOne && !isTwo && isThree) {
// 0 1 0
_viewOneWidth.constant = viewBottomWidthConstant / 2;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = viewBottomWidthConstant / 2;
}
else if (isOne && !isTwo && !isThree) {
// 0 1 1
_viewOneWidth.constant = viewBottomWidthConstant;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = 0;
}
else if (!isOne && isTwo && isThree) {
// 1 0 0
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = viewBottomWidthConstant / 2;
_viewThreeWidth.constant = viewBottomWidthConstant / 2;
}
else if (!isOne && isTwo && !isThree) {
// 1 0 1
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = viewBottomWidthConstant;
_viewThreeWidth.constant = 0;
}
else if (!isOne && !isTwo && isThree) {
// 1 1 0
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = viewBottomWidthConstant;
}
else if (isOne && isTwo && isThree) {
// 1 1 1
_viewOneWidth.constant = 0;
_viewTwoWidth.constant = 0;
_viewThreeWidth.constant = 0;
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end
Hope So this logic will help some one.
回答12:
In my case I set the constant of the height constraint to 0.0f
and also set the hidden
property to YES
.
To show the view (with the subviews) again I did the opposite: I set the height constant to a non-zero value and set the hidden
property to NO
.
回答13:
I will use horizontal stackview. It can remove the frame when the subview is hidden.
In image below, the red view is the actual container for your content and has 10pt trailing space to orange superview (ShowHideView), then just connect ShowHideView to IBOutlet and show/hide/remove it programatically.
- This is when the view is visible/installed.
- This is when the view is hidden/not-installed.
回答14:
This my another solution using priority constraint. The idea is set the width to 0.
create container view (orange) and set width.
create content view (red) and set trailing space 10pt to superview (orange). Notice trailing space constraints, there are 2 trailing constraint with different priority. Low(=10) and High(<=10). This is important to avoid ambiguity.
Set orange view's width to 0 to hide the view.
回答15:
As no_scene suggested, you can definitely do this by changing the priority of the constraint at runtime. This was much easier for me because I had more than one blocking view which would have to be removed.
Here's a snippet using ReactiveCocoa:
RACSignal* isViewOneHiddenSignal = RACObserve(self.viewModel, isViewOneHidden);
RACSignal* isViewTwoHiddenSignal = RACObserve(self.viewModel, isViewTwoHidden);
RACSignal* isViewThreeHiddenSignal = RACObserve(self.viewModel, isViewThreeHidden);
RAC(self.viewOne, hidden) = isViewOneHiddenSignal;
RAC(self.viewTwo, hidden) = isViewTwoHiddenSignal;
RAC(self.viewThree, hidden) = isViewThreeHiddenSignal;
RAC(self.viewFourBottomConstraint, priority) = [[[[RACSignal
combineLatest:@[isViewOneHiddenSignal,
isViewTwoHiddenSignal,
isViewThreeHiddenSignal]]
and]
distinctUntilChanged]
map:^id(NSNumber* allAreHidden) {
return [allAreHidden boolValue] ? @(780) : @(UILayoutPriorityDefaultHigh);
}];
RACSignal* updateFramesSignal = [RACObserve(self.viewFourBottomConstraint, priority) distinctUntilChanged];
[updateFramesSignal
subscribeNext:^(id x) {
@strongify(self);
[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:0.3 animations:^{
[self.view layoutIfNeeded];
}];
}];
回答16:
In case this helps someone, I built a helper class for using visual format constraints. I'm using it in my current app.
AutolayoutHelper
It might be a bit tailored to my needs, but you might find it useful or you might want to modify it and create your own helper.
I have to thank Tim for his answer above, this answer about UIScrollView and also this tutorial.
回答17:
Here's how I would re-align my uiviews to get your solution:
- Drag drop one UIImageView and place it to the left.
- Drag drop one UIView and place it to the right of UIImageView.
- Drag drop two UILabels inside that UIView whose leading and trailing constraints are zero.
- Set the leading constraint of UIView containing 2 labels to superview instead of UIImagView.
- IF UIImageView is hidden, set the leading constraint constant to 10 px to superview. ELSE, set the leading constraint constant to 10 px + UIImageView.width + 10 px.
I created a thumb rule of my own. Whenever you have to hide / show any uiview whose constraints might be affected, add all the affected / dependent subviews inside a uiview and update its leading / trailing / top / bottom constraint constant programmatically.
回答18:
This is an old question but still I hope it will helps. Coming from Android, in this platform you have an handy method isVisible
to hide it from the view but also not have the frame considered when the autolayout draw the view.
using extension and "extend" uiview you could do a similar function in ios (not sure why it is not in UIKit already) here an implementation in swift 3:
func isVisible(_ isVisible: Bool) {
self.isHidden = !isVisible
self.translatesAutoresizingMaskIntoConstraints = isVisible
if isVisible { //if visible we remove the hight constraint
if let constraint = (self.constraints.filter{$0.firstAttribute == .height}.first){
self.removeConstraint(constraint)
}
} else { //if not visible we add a constraint to force the view to have a hight set to 0
let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal , toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 0)
self.addConstraint(height)
}
self.layoutIfNeeded()
}
回答19:
the proper way to do it is to disable constraints with isActive = false. note however that deactivating a constraint removes and releases it, so you have to have strong outlets for them.
回答20:
I thing this is most simplest answer please check it for it's working well
StackFullView.layer.isHidden = true
Task_TopSpaceSections.constant = 0. //your constrain of top view
check here https://www.youtube.com/watch?v=EBulMWMoFuw
来源:https://stackoverflow.com/questions/18065938/how-to-use-auto-layout-to-move-other-views-when-a-view-is-hidden