Представление с динамическим размером на основе дочернего контроллера представления

#ios #swift #autolayout

#iOS #swift #автоматическое описание

Вопрос:

У меня есть пользовательский контроллер представления, используемый в качестве контроллера дочернего представления:

 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
        }
    }
}
 

Я пытаюсь динамически изменять размер container представления в основном VC на основе вычисленной высоты дочернего элемента. Вызывается preferredContentSizeDidChange метод, но при вычислении высоты дочернего 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 ? Как мне рассчитать высоту дочернего представления на основе его содержимого?

Комментарии:

1. Установлен ли в вашем дочернем контроллере представления соответствующие ограничения автозапуска? Может ли он действительно оценить правильную высоту в вашем calculatePreferredSize методе? systemLayoutSizeFitting вычисляет минимальный размер, необходимый для удовлетворения всех ограничений. можете ли вы поставить точку останова и проверить, что вы возвращаете в конце calculatePreferredSize метода?

2. Привет. Извините, но я на самом деле отвечаю на все это в вопросе. 1. Да, ограничения установлены для newView . 2. Нет, это невозможно — вот почему я задаю вопрос. 3. Да, я сделал это и включил вывод журнала. Рамка имеет правильные размеры (например, ширина 350 и высота 123), но пока systemLayoutSizeFitting не вычисляет ее правильно. Предположим, я вызываю его в неправильной последовательности или что-то в этом роде, потому что подвиды есть и даже видны на экране в правильном размере.

3. UIView.layoutFittingCompressedSize всегда будет (0, 0) , потому что это константа.

4. @JurajBlahunka — спасибо, но я не думаю, что это правильно. Смотрите Документацию Apple по адресу developer.apple.com/documentation/uikit/uiview /… или это видео на отметке 37:30 developer.apple.com/videos/play/wwdc2018/220

5. Правильно, UIView.layoutFittingCompressedSize это руководство (целевой размер) для systemLayoutSizeFitting метода. Метод не будет изменен. Что вам нужно проверить, так это возвращаемое значение systemLayoutSizeFitting метода.

Ответ №1:

В 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 точки, но ограничение высоты контейнера по-прежнему равно 0, прежде чем мы вызовем preferredContentSizeDidChange метод.

 containerHeightConstraint.priority = .defaultLow
 

Комментарии:

1. Я явно чего-то не понимаю. Добавление этого 5-го ограничения приводит к неудовлетворительной ошибке автоматического ведения журнала макета, поскольку у представления уже установлена высота — 123, поэтому его нельзя привязать как к верхнему, так и к нижнему якорю родительского представления. Извините, но, возможно, я чего-то здесь не понимаю.

2. Добавление вышеизложенного вычисляет preferredContentSizeDidChange , но я думаю, что это неправильный подход, поскольку он приводит к тому, что автоматическая компоновка должна нарушать ограничение. и в этом случае это нарушает ограничение высоты, поэтому конечным результатом теперь является дочерний контроллер представления с высотой 0 при проверке иерархии представлений отладки в Xcode.

3. Спасибо. Несмотря на то, что симулятор теперь отображается корректно, все еще существуют ошибки автоматического ограничения компоновки, потому что теперь у newView нас есть ограничения with на все края родительского представления, а также на высоту 123. Я также не совсем понимаю необходимость наличия ограничений 5 для newView . Но я ценю вашу помощь.

4. Глядя на это еще немного — я приведу вам другой пример. Если я перемещу newView код и ограничения в дочерний VC, а затем создам экземпляр этого контроллера представления в качестве основного контроллера представления, теперь синий вид заполняет весь экран, потому что мы привязали его ко всем краям. Вот почему я, хотя исходные ограничения, которые у меня были, — это правильный путь — сохраняет правильную высоту на уровне 123 и имеет синий newView цвет вверху. Но при внедрении в качестве дочернего VC мы вычисляем размер содержимого этого VC, используя systemLayoutSizeFitting и UIView.layoutFittingCompressedSize

5. Я думаю, что на данный момент лучше всего переформулировать ваш вопрос и, возможно, спросить об основных концепциях отдельно? Этот поток выходит за рамки исходного вопроса.