在滚动视图中使用动态大小的控制器调整容器视图的大小

Sizing a Container View with a controller of dynamic size inside a scrollview

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

View Controller A是scrollview,包含container view,更多内容如下。

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

我在获取 B 的动态大小以自动设置 A 中容器视图的大小时遇到​​一些问题。但是,如果我在 A 中的容器视图上设置高度限制

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

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

有没有办法在不设置高度限制的情况下,根据View Controller B的大小来调整A中的Container View的大小?

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

您所要做的就是告诉系统它不应添加模仿 Interface Builder 中根视图的固定框架集的约束。执行此操作的最佳位置是在触发嵌入转场时在容器视图控制器中:

- (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 优先级约束。

根据@Mischa 的回答,我可以根据内容动态调整 containerView 的高度:

在containerView的viewController中写入:

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

并注意 IB 中的垂直约束已全部设置。这样做你不需要从视图控制器外部设置 view.translatesAutoresizingMaskIntoConstraints = false。

在我的例子中,我试图将容器的大小调整为 viewController 内的 tableView。因为 tableView 有一个灵活的高度取决于它的 superview(所以对于 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约束的常量。

Swift4,Xcode9

仅接受的答案并没有解决我的问题。

我的层次结构:滚动视图 --> 内容视图 (UIView) --> 视图 |容器视图 |其他观点。

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

  1. ScrollView:顶部、底部、前导、尾随到超级视图(安全区域)
  2. 内容视图:顶部、底部、前导、尾随、与 ScrollView 等宽,但也等高 (优先级较低的约束:250) .
  3. Views: 正常的自动布局约束。
  4. 容器视图:相邻视图的顶部、底部、前导和尾随到安全区域。
  5. Container View's embedded VC: Everything constraint vertically connected vertically with the bottom constraint set to a lower priority, 但更大比内容视图的等高! 在这种情况下,500 的优先级起到了作用。
  6. 如其他答案所述,在 prepareForSegue()loadView() 中设置 view.translatesAutoresizingMaskIntoConstraints = false

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

基于@Mischa 对这个问题的出色解决方案,如果容器嵌入了一个动态大小的 UITableView,那么您需要考虑覆盖它的 contentSize 和 intrinsicContentSize,以及将 translatesAutoresizingMaskIntoConstraints = false 设置为根据接受的答案。

当父视图加载并设置容器视图时,每个UITableViewCells的固有高度被推断为0,因此UITableViewDelegate方法:

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

不会被调用。这使得 UITableView 看起来好像没有内容。

将固有内容大小覆盖为 return 实际内容大小意味着 tableview 以其内容所需的大小显示。

Emilio Peláez 的精彩 article 就此主题进行了更深入的探讨。

我试过这个解决方案并为我工作。
在目标(子)视图控制器中尝试像这样访问父视图控制器:

if let parentVC = self.parent as? EmbededContinerViewController {               
   if let myParent = parentVC.parent as? ParentViewController {
      myParent.subViewHeight.constant += 2000
      myParent.subView.layoutIfNeeded()
   }

}

也许这不是正常的解决方案,但对我有用并解决了我的问题。
我在 Swift 5.1 .

中尝试了这段代码