如何在隐藏视图时使用自动布局移动其他视图?

Guf*_*ros 321 iphone cocoa-touch interface-builder ios autolayout

我在IB中设计了自定义Cell,将其子类化并将我的插座连接到我的自定义类.我在单元格内容中有三个子视图:UIView(cdView)和两个标签(titleLabel和emailLabel).根据每行可用的数据,有时我想在我的单元格中显示UIView和两个标签,有时只有两个标签.我想要做的是设置约束,如果我将UIView属性设置为隐藏或我将从superview中删除它,两个标签将移动到左侧.我尝试将UIView前导约束设置为10px的Superview(单元格内容)和UILabels将10 px的约束引导到下一个视图(UIView).稍后在我的代码中

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(IndexPath *)indexPath {
...
Record *record = [self.records objectAtIndex:indexPath.row];

if ([record.imageURL is equalToString:@""]) {
     cell.cdView.hidden = YES;
}
Run Code Online (Sandbox Code Playgroud)

我隐藏了我的cell.cdView,我希望标签向左移动,但是它们在Cell中保持相同的位置.我试图从superview中删除cell.cdView,但它也没有用.我附上图片来澄清我的意思.

细胞

我知道如何以编程方式执行此操作,而我不是在寻找解决方案.我想要的是在IB中设置约束,并且如果删除或隐藏其他视图,我希望我的子视图将动态移动.是否可以在具有自动布局的IB中执行此操作?

.....
Run Code Online (Sandbox Code Playgroud)

Tim*_*Tim 361

这是可能的,但你必须做一些额外的工作.有一些概念性的东西可以先摆脱困境:

  • 隐藏的视图,即使它们不绘制,仍然参与自动布局,并且通常保留其框架,在其位置留下其他相关视图.
  • 从超级视图中删除视图时,也会从该视图层次结构中删除所有相关约束.

在您的情况下,这可能意味着:

  • 如果将左侧视图设置为隐藏,则标签会保留在原位,因为左侧视图仍占用空间(即使它不可见).
  • 如果删除左视图,则标签可能会受到模糊约束,因为您的标签左边缘不再有约束.

您需要做的是明智地过度约束您的标签.单独留下现有约束(10pts空间到另一个视图),但添加另一个约束:使标签的左边缘与超级视图的左边缘相差10pts,具有非必需的优先级(默认的高优先级可能会很好).

然后,当您希望它们向左移动时,完全删除左视图.左视图的强制10pt约束将与其相关的视图一起消失,并且您将只留下一个高优先级约束,标签距其超视图10pts.在下一个布局过程中,这应该使它们向左扩展,直到它们填充超视图的宽度,但是围绕边缘的间距.

一个重要的警告:如果您希望将左侧视图放回到图片中,则不仅需要将其添加回视图层次结构中,而且还必须同时重新建立其所有约束.这意味着只要再次显示该视图,您就需要一种方法将视图及其标签之间的10pt间距约束放回去.

  • 我恭敬地不同意.如果您(例如)将视图的宽度设置为0,则会遇到两个问题.首先,你现在在superview和可见视图之间有两个间距:`| - (space) - [hidden(0)] - (space) - [visible]`实际上是`| - (2*space) - [visible ]`.其次,该视图可能会根据自己的视图子树和约束开始抛出约束违规 - 您无法保证可以任意约束0宽度的视图并让它继续工作. (24认同)
  • 虽然这个答案肯定会有效,但IMO过度约束处理各种用例似乎是一种代码味道 - 特别是因为您必须重新建立所有已删除视图的约束,以便再次显示视图. (6认同)

Max*_*eod 202

在运行时添加或删除约束是一项可能影响性能的重量级操作.但是,有一个更简单的选择.

对于要隐藏的视图,请设置宽度约束.使用与该视图的前导水平间隙约束其他视图.

要隐藏,.constant请将宽度约束更新为0.f. 其他视图将自动向左移动以占据位置.

有关详细信息,请参阅我的其他答案:

如何在运行时更改标签约束?

  • 这个解决方案的唯一问题是左边的边距将是你想要的两倍,所以我也会更新其中一个约束,但即便如此,我认为这比删除子视图要少. (29认同)
  • @skinsfan00atg如果您使用的是具有内在内容大小的视图,则无法使用此解决方案. (2认同)
  • @MaxMacLeod如果您减少了内容拥抱优先级,那么您没有使用内在内容大小,而是使用约束指示的大小. (2认同)
  • @MaxMacLeod好的,我明白你的意思.当您想隐藏视图时,需要在代码中将压缩阻力优先级设置为0(不是内容拥抱),并且当您想要再次显示时,还原该值.除此之外,您需要在界面构建器中添加约束以将视图的大小设置为0.无需在代码中触摸此约束. (2认同)

Sil*_*ril 80

对于仅支持iOS 8+的用户,有一个新的布尔属性处于活动状态.它将有助于动态地仅启用所需的约束

PS约束出口必须坚固,不弱,

例如:

@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
}
Run Code Online (Sandbox Code Playgroud)

另外,要修复故事板中的错误,您需要取消Installed选中其中一个约束的复选框.

UIStackView (iOS 9+)还有
一个选项是封装您的视图UIStackView.一旦隐藏视图UIStackView将自动更新布局

  • 这是因为未加载或取消分配已停用的约束.你的约束出口应该是强大的,而不是弱的 (10认同)
  • 似乎过度约束 + 设置约束活动可能是最合理的答案。 (2认同)

sid*_*ick 59

UIStackView在其hidden任何子视图(iOS 9+)上更改属性时,会自动重新定位其视图.

UIView.animateWithDuration(1.0) { () -> Void in
   self.mySubview.hidden = !self.mySubview.hidden
}
Run Code Online (Sandbox Code Playgroud)

在这个WWDC视频中跳转到11:48进行演示:

汽车布局之谜,第1部分

  • 这应该是公认的答案.继苹果推荐wwdc 2015接口构建器设计之后. (4认同)
  • @bagage除非你是facebook或google,否则你可以放弃它.随着iOS 9的覆盖范围,您将支持超过90%的设备,绰绰有余.下面的支持将削弱您的开发过程并阻止您使用最新功能imo (4认同)

Rob*_*ins 16

我的项目使用自定义@IBDesignable子类UILabel(以确保颜色,字体,插图等的一致性),我已经实现了类似以下内容:

override func intrinsicContentSize() -> CGSize {
    if hidden {
        return CGSizeZero
    } else {
        return super.intrinsicContentSize()
    }
}
Run Code Online (Sandbox Code Playgroud)

这允许标签子类参与自动布局,但隐藏时不占用空间.


jte*_*rry 13

对于Google员工:在Max的答案基础上,为了解决许多人注意到的填充问题,我只是增加了标签的高度,并将该高度用作分隔符而不是实际的填充.对于包含视图的任何场景,可以扩展此想法.

这是一个简单的例子:

IB截图

在这种情况下,我将Author标签的高度映射到适当的IBOutlet:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
Run Code Online (Sandbox Code Playgroud)

当我将约束的高度设置为时0.0f,我们保留"填充",因为" 播放"按钮的高度允许它.


Yog*_*til 9

只需使用 UIStackView ,一切都会正常工作。无需担心其他约束,UIStackView 会自动处理空间。


小智 8

将uiview和标签之间的约束连接为IBOutlet,并在设置hidden = YES时将优先级成员设置为较小的值

  • 我已经有了这种方法.我有一个使用标签和按钮的情况,我需要隐藏按钮并扩展标签.我有两个约束,一个最初优先级为751,另一个优先级为750.然后当我隐藏按钮时,我会优先处理优先级,标签的长度会增加.应该注意的是,如果您尝试将更高优先级设置为1000,则会出现错误"不支持将优先级从需要变为不安装约束(或反之亦然)".所以不要,你似乎没事.Xcode 5.1/viewDidLoad. (7认同)
  • 一旦建立,您就无法调整NSLayoutConstraint的优先级; 您必须删除并读取具有不同优先级的新约束. (3认同)

Mic*_*son 8

我最终做的是创建2个xib.一个是左视图,一个没有它.我在控制器中注册了两个,然后决定在cellForRowAtIndexPath期间使用哪个.

它们使用相同的UITableViewCell类.缺点是xib之间存在一些重复内容,但这些单元格非常基本.好处是我没有一堆代码来手动管理删除视图,更新约束等.

一般来说,这可能是一个更好的解决方案,因为它们在技术上是不同的布局,因此应该有不同的xib.

[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];
Run Code Online (Sandbox Code Playgroud)


Mit*_*iya 7

在这种情况下,我将Author标签的高度映射到适当的IBOutlet:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;
Run Code Online (Sandbox Code Playgroud)

当我将约束的高度设置为0.0f时,我们保留"填充",因为"播放"按钮的高度允许它.

cell.authorLabelHeight.constant = 0;
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述 在此输入图像描述


Wex*_*Wex 7

对于此特定布局,要使用的约束是正在隐藏的视图上的“主导”约束。不过,下面的理论在各个方面都适用。

1:设置所有约束,使其在所有视图都可见时呈现出您想要的外观。

2:向要隐藏的视图添加第二个“前导”约束。这将暂时打破限制。

3:将原始主导约束的优先级更改为“999” - 这将优先考虑新约束,该约束将为 1000,并且不会再破坏任何约束。

4:将新约束从“leading=leading”更改为“trailing=leading”。这会将您想要隐藏的视图移离其父级的前缘,将其移开。

5:切换新约束的 isActive 值现在将在视图内或视图外切换。在将可见性设置为 true/false 的同时将其设置为 true/false。例如:

@IBOutlet var avatar:UIImage!
@IBOutlet var avatarLeadHid:NSLayoutConstraint!

func hideAvatar() {
  self.avatar.isHidden = true
  self.avatarLeadHid.isActive = true
}

func showAvatar() {
  self.avatar.isHidden = false
  self.avatarLeadHid.isActive = false
}
Run Code Online (Sandbox Code Playgroud)

奖励:您可以调整新隐藏器约束的“常量”值,以更改隐藏视图时要使用的填充/边距。该值可以为负值。

额外奖励:只需切换隐藏器约束上的“已安装”复选框,就可以在界面生成器中查看布局的外观,而无需运行任何代码。

进一步的帮助:我制作了一个视频,展示了我比一系列要点做得更好的地方: https: //youtu.be/3tGEwqtQ-iU


Rom*_*kin 6

使用两个UIStackView水平和垂直,当隐藏堆栈中的某个子视图时,其他堆栈子视图将被移动,使用分布 - >为具有两个UILabel的垂直堆栈预先填充,并且需要设置第一个UIView的宽度和高度.在此输入图像描述