基于 Child 视图控制器的动态大小视图

Dynamically Sized View based on Child View Controller

我有一个自定义视图控制器用作 child 视图控制器:

class ChildViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .green
    }
    

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        calculatePreferredSize()
    }
    
    
    func calculatePreferredSize() {
        let targetSize = CGSize(width: view.bounds.width,
                                height: UIView.layoutFittingCompressedSize.height)
        preferredContentSize = view.systemLayoutSizeFitting(targetSize)
    }
}

然后在主视图控制器中,我有这段代码:

class ViewController: UIViewController {

    var container : UIView!
    var childVC   : ChildViewController!
    
    var containerHeightConstraint: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .purple
        
        // setup container to hold child vc
        container = UIView()
        container.backgroundColor = .systemPink
        container.translatesAutoresizingMaskIntoConstraints = false
    
        view.addSubview(container)
        container.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
        container.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20).isActive = true
        container.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20).isActive = true

        containerHeightConstraint = NSLayoutConstraint()
        containerHeightConstraint = container.heightAnchor.constraint(equalToConstant: 0)
        containerHeightConstraint.isActive = true
        
        // setup child vc
        childVC = ChildViewController()
        addChild(childVC)
        container.addSubview(childVC.view)
        childVC.view.frame = container.bounds
        childVC.didMove(toParent: self)
        
        // add contents into the child vc
        let newView = UIView()
        childVC.view.addSubview(newView)
        newView.backgroundColor = .systemBlue
        newView.translatesAutoresizingMaskIntoConstraints = false

        NSLayoutConstraint.activate([
            newView.topAnchor.constraint(equalTo: newView.superview!.topAnchor),
            newView.leadingAnchor.constraint(equalTo: newView.superview!.leadingAnchor),
            newView.trailingAnchor.constraint(equalTo: newView.superview!.trailingAnchor),
            newView.heightAnchor.constraint(equalToConstant: 123),
        ])
    }
   
    
    override func preferredContentSizeDidChange(forChildContentContainer container: UIContentContainer) {
        super.preferredContentSizeDidChange(forChildContentContainer: container)
        if (container as? ChildViewController) != nil {
            containerHeightConstraint.constant = container.preferredContentSize.height
        }
    }
}

我正在尝试根据 child 的计算高度在主 VC 中动态调整 container 视图的大小。正在调用 preferredContentSizeDidChange 方法,但在我计算 child VC 的高度(使用 UIView.layoutFittingCompressedSize)时,我总是返回 0。即使我'已经检查了添加到该视图的视图框架,它具有正确的框架高度(在本例中为 123)。如下面的输出日志所示:

(lldb) po view.subviews
▿ 1 element
  - 0 : <UIView: 0x12251cd40; frame = (0 0; 350 123); layer = <CALayer: 0x6000007a0e60>>

(lldb) po UIView.layoutFittingCompressedSize
▿ (0.0, 0.0)
  - width : 0.0
  - height : 0.0

下面是模拟器的截图。

我使用 UIView.layoutFittingCompressedSize 不正确吗?如何根据内容计算 child 视图的高度?

Autolayout 无法计算 newView 内容高度,因为它缺少 Y 轴上的约束来求解方程。

newView 只定义了这些约束:顶部、前导、尾随和高度。 它缺少底部约束:

newView.bottomAnchor.constraint(equalTo: newView.superview!.bottomAnchor).isActive = true

完整的约束集如下所示:

NSLayoutConstraint.activate([
    newView.topAnchor.constraint(equalTo: newView.superview!.topAnchor),
    newView.leadingAnchor.constraint(equalTo: newView.superview!.leadingAnchor),
    newView.trailingAnchor.constraint(equalTo: newView.superview!.trailingAnchor),
    newView.heightAnchor.constraint(equalToConstant: 123),
    newView.bottomAnchor.constraint(equalTo: newView.superview!.bottomAnchor)
])

然后当我在 preferredContentSizeDidChange 中放置断点时,我可以打印 container.preferredContentSize.height,即 123.0

编辑 为了避免约束破坏,我们还需要为 childVC.view 使用自动布局。现在它正在使用自动调整掩码,它只从上到下流动并创建优先级为 1000 的约束。

childVC.view.frame = container.bounds

需要替换为

childVC.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    childVC.view.topAnchor.constraint(equalTo: container.topAnchor),
    childVC.view.leadingAnchor.constraint(equalTo: container.leadingAnchor),
    childVC.view.trailingAnchor.constraint(equalTo: container.trailingAnchor),
    childVC.view.bottomAnchor.constraint(equalTo: container.bottomAnchor)
])

containerHeightConstraint 需要降低 0 高度约束的优先级,否则系统总是会发现约束不明确——子控制器想要 123 点高,但容器高度约束是在调用 preferredContentSizeDidChange 方法之前仍为 0。

containerHeightConstraint.priority = .defaultLow