在滚动视图中放置一个动态大小的控制器的容器视图的大小调整

78

我正在尝试创建一个包含具有动态高度的控制器的容器视图,并将其放置在UIScrollView中,使用自动布局自动调整大小。

说明设置的Storyboard

视图控制器A是滚动视图,其中包括容器视图以及更多下方的内容。

视图控制器B是我想要其具有动态大小并且所有内容都能在视图控制器A的滚动视图中完全显示的视图控制器。

我遇到了一些问题,无法使B的动态大小自动设置A中的容器视图的大小。但是,如果我在A中设置容器视图的高度约束 例250的A中容器视图的高度

如果视图控制器B的高度也为250,则它将是预期的输出。对于高度1000,它也可以正常工作,因此据我所知,所有自动布局约束都已正确设置。不幸的是,由于高度实际上应该是动态的,我想完全避免设置高度约束。

我不确定是否有任何视图控制器B的设置,可以使其根据其内容自动更新其大小,或者是否有任何其他我错过的技巧。任何帮助都将不胜感激!

是否有任何方法可以根据视图控制器B的大小来调整A中容器视图的大小,而不设置高度约束?


你可以将高度约束设置为IBOutlet,并在代码中动态地进行调整。 - Randy
这是我接近解决方案的最佳方式,但这是否是最简单的方法呢?我希望有些我错过的东西可以比手动设置高度约束更轻松地解决问题。 - TemptingFriendlyGrison
1
我认为你也可以在这里使用preferredContentSize - 只需不断更改它,你知道的吧? - Fattie
5个回答

120

是的,有这样的方法。我在自己的一个项目中成功实现了这种行为。

你需要告诉系统,在Interface Builder中不要添加模拟根视图固定帧的约束。最好的地方是在你的容器视图控制器中,当嵌入式segue被触发时:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // You might want to check if this is your embed segue here
    // in case there are other segues triggered from this view controller. 
    segue.destinationViewController.view.translatesAutoresizingMaskIntoConstraints = NO;
}

重要提示:

必须确保你加载到容器中的视图从上到下有约束,并且需要将其中一个垂直约束的优先级设置为低于1000的值。(使用底部约束是一个好习惯。)这是必要的,否则Interface Builder会抱怨 - 有充分的理由:

在设计时,你的根视图具有固定的大小(高度)。现在,如果你所有的子视图都具有固定的高度,并且被连接到具有相同优先级的固定约束,除非你的固定根视图的高度恰好与子视图和垂直约束的总高度完全匹配,否则不可能满足所有这些要求。如果你将其中一个约束的优先级降低到999,Interface Builder 就知道要打破哪个约束。然而,在运行时,当 translatesAutoresizingMaskIntoConstraints 属性按上述方式设置时,你的根视图不再具有固定的框架,系统会使用你的优先级为999的约束。

Interface Builder Screenshot


2
这个很好用,谢谢!在编辑器中,滚动视图有一些约束问题,但如果我在容器上设置一个高度约束并在构建时删除它们,这些问题就解决了。 - TemptingFriendlyGrison
14
如果您前往Interface Builder中的“Size Inspector”,向下滚动并从“Intrinsic Content Size”下拉菜单中选择“占位符”,则应该能够解决那些约束问题,而无需在容器视图上设置和删除约束。然后,您可以为容器视图设置固定的内在大小,这仅由Interface Builder用于布局视图,但不会在运行时应用。 - Mischa
在iOS 9上工作时,我发现在父视图控制器中将translatesAutoresizingMaskIntoConstraints = NO设置(而不是在子容器VC中)可以解决问题。我保持了底部约束的优先级为1000,这允许整个容器视图增长。否则,容器视图的内容会超过我的容器视图底部,并且我会看到布局中的其他奇怪问题。谢谢你找出了这个问题!!! - Kento
这对我不起作用,使用容器视图的IBOutlet并调整高度可以解决问题。 - siva krishna
有人能向我解释一件事吗:因此,容器视图将子视图控制器的视图嵌入其中(在使用故事板时是这种情况)...如果 translatesAutoresizingMaskIntoConstraints 关闭了,根视图如何将其顶部和底部边缘固定在容器视图上?我看到我们在根视图本身内添加约束,但没有任何东西告诉根视图如何在容器中定位自己。 - Eman Harout
显示剩余7条评论

30

从 @Mischa 的答案中,我学会了如何使 containerView 的高度根据其内容动态变化,方法如下:

在 containerView 的 viewController 中写入以下内容:

  override func loadView() {
    super.loadView()
    view.translatesAutoresizingMaskIntoConstraints = false
  }

确保IB中的垂直约束都设置好。这样做可以避免从视图控制器外部设置view.translatesAutoresizingMaskIntoConstraints = false。

在我的情况下,我试图调整包含tableView的容器大小,以适应其父视图的可变高度(所以在IB中一切都正常),我通过以下代码完成了垂直约束:

  @IBOutlet private var tableView: UITableView! {
    didSet {
      tableView.addConstraint(tableViewHeight)
    }
  }
  private lazy var tableViewHeight: NSLayoutConstraint = NSLayoutConstraint(item: self.tableView, attribute: NSLayoutAttribute.Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 0)

观察tableview的contentSize高度,并在需要时通过编程方式调整tableViewHeight约束的常量。


感谢@acecilia,这确实是一个很棒的提示。干杯! - Fattie

17

Swift 4,Xcode 9

仅凭接受的答案不能解决我的问题。

我的层次结构:ScrollView -> Content View (UIView) -> Views | Container View | Other Views。

我必须添加以下约束条件,以使ScrollView和Container都能动态调整:

  1. ScrollView:Top,Bottom,Leading,Trailing对于Superview (Safe Areas)
  2. Content View:Top,Bottom,Leading,Trailing,Equal Width to ScrollView,但也具有Equal Heights (constraint with a lower priority: 250).
  3. Views:normal自动布局约束。
  4. Container View:Top,Bottom to neighbor views,Leading and Trailing to Safe Area。
  5. Container View's embedded VC:Everything constraint connected vertically with the bottom constraint set to a lower priority, but bigger than the Content View's Equal Height one! In this case, a priority of 500 did the trick.
  6. 按照其他答案所述,在prepareForSegue()loadView()中设置view.translatesAutoresizingMaskIntoConstraints = false

现在我有了一个可以动态调整大小的容器视图,放在一个自动调整大小的滚动视图中。


嗨,Teodor,我看了你的回答,我认为你可以帮助我解决我的问题:https://stackoverflow.com/questions/48797508/twitter-profile-effect 谢谢你的帮助。 - delarcomarta
有 GitHub 的示例吗? - Muhammad Hassan
当ContainerView中存在可滚动视图时,它无法正常工作。 - Hamish
1
如果可以的话,我会给这个点赞5次。这个逐步回答很简单,解决了因IB限制问题浪费了3个小时的困难。 - TM Lynch
惊人的解决方案!!! 对我有用。但是现在我又遇到了另一个问题。我的嵌入式表视图控制器可以在原地滚动,但是滚动视图已经停止滚动。 - jerry

4
建立在 @Mischa 的优秀解决方案基础上,如果容器包含一个动态大小的 UITableView,则需要查看重写其 contentSize 和 intrinsicContentSize 方法,以及设置 translatesAutoresizingMaskIntoConstraints = false,正如被接受的答案所述。当父视图加载并设置容器视图时,UITableViewCells 每个固有高度都被推断为 0,因此不会调用 UITableViewDelegate 方法:func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell。这使得 UITableView 看起来好像没有内容。重写内在内容大小以返回实际内容大小意味着表格视图将显示其内容所需的大小。Emilio Peláez 的一篇很棒的 文章 对这个主题进行了更深入的探讨。

1
我尝试了这个解决方案,对我有效。
在目标(子)视图控制器中,尝试像这样访问父视图控制器:
if let parentVC = self.parent as? EmbededContinerViewController {               
   if let myParent = parentVC.parent as? ParentViewController {
      myParent.subViewHeight.constant += 2000
      myParent.subView.layoutIfNeeded()
   }

}

也许这不是普遍的解决方案,但对我有效并解决了我的问题。
我在Swift 5.1中尝试了这段代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接