Границы подпредставления равны нулю в layoutSubviews()

Границы подпредставления пользовательского UIView кажутся равными 0 в layoutSubviews(), поэтому использование границ в layoutSubviews() является проблемой.

Чтобы показать проблему, я выложил демо на GitHub: SOBoundsAreZero

Вот прямая ссылка на реализацию пользовательского представления: DemoView.swift

Структура пользовательского представления "DemoView" такова:

DemoView
    firstLevelSubview
        secondLevelSubview

Эта структура создается программно с использованием Auto Layout.

Когда вызывается layoutSubviews(), представление и firstLevelSubview имеют ожидаемые границы, но границы secondLevelSubview равны 0.

Я ожидал, что все подпредставления, использующие Auto Layout, будут иметь правильные границы, по крайней мере, в последнем вызове layoutSubviews.

Структура является абстракцией реального случая. Чтобы избежать этой проблемы, secondLevelSubview можно добавить в качестве подпредставления первого уровня в DemoView. Хотя, это то, что невозможно в реальном случае.

Я чувствую, что мне здесь не хватает чего-то простого, даже если это ожидаемое поведение.

Вы добавили ограничение и установили translatesAutoresizingMaskIntoConstraints = false?

Vicaren 05.07.2019 12:29

@Vicaren, да, все готово. Я добавил ссылку на реализацию для удобства.

Felix Lieb 05.07.2019 12:52
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
2
3 067
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Во-первых, ваше представление интерпретируется без проблем, потому что у вас есть инициализатор, основанный на «CGRect», поэтому он получает свое измерение! Второй, Ваше первое подпредставление инициализируется после того, как его родительское представление получило свои размеры. Размеры первого подвида зависят от готовых размеров родительского вида. Так что здесь нет проблем, как вы упомянули. Но пока ваше первое подпредставление пытается получить свои размеры, ваше второе подпредставление также начинает получать свои размеры на основе первого подпредставления, которое еще не готово, поэтому оно ничего не получило. Я предлагаю сделать разные функции для каждого подпредставления. Сделайте свой объект из класса представления с его инициализатором фрейма в вашем контроллере представления. В «viewDidLoad» вызовите первую функцию подпредставления, а в «viewDidLoadSubView» вызовите вторую функцию подпредставления. Надеюсь, поможет.

Ответ принят как подходящий

Я смог исправить это с помощью вызова secondLevelSubview.layoutIfNeeded() в layoutSubviews().

override func layoutSubviews() {
    super.layoutSubviews()

    secondLevelSubview.layoutIfNeeded()

    firstLevelSubview.layer.cornerRadius = bounds.width / 2
    secondLevelSubview.layer.cornerRadius = secondLevelSubview.bounds.width / 2

    print("bounds.width: \(bounds.width), contentView.bounds.width: \(firstLevelSubview.bounds.width), backgroundView.bounds.width: \(secondLevelSubview.bounds.width)")
}

Описание layoutIfNeeded():

Use this method to force the view to update its layout immediately. When using Auto Layout, the layout engine updates the position of views as needed to satisfy changes in constraints. Using the view that receives the message as the root view, this method lays out the view subtree starting at the root. If no layout updates are pending, this method exits without modifying the layout or calling any layout-related callbacks.

Итак, по сути, у вас есть проблема с заказом здесь. Подвид запланирован на верстку, и Автоматический макет до него доберется, но пока этого не сделал. Вызывая layoutIfNeeded(), вы говорите Автоматический макет немедленно выполнить отложенный макет, чтобы вы могли получить обновленную информацию о кадре.

Примечание: Вы также можете просто вызвать self.layoutIfNeeded(), и это сделает макет DemoView и все его подвиды. Это было бы полезно, если бы у вас было много таких подпредставлений и вы не хотели бы вызывать layoutIfNeeded() для каждого из них.

Спасибо @vacawama, для меня это имеет смысл. Вызов layoutIfNeeded() в функции, которая устанавливает ограничения, после этого имеет смысл для меня. Я думаю, что это была «простая вещь», которую я пропустил.

Felix Lieb 05.07.2019 13:46

Интересно, что если вы позвоните layoutIfNeeded() в setupViews(), layoutSubviews() будет вызвана дважды, сначала с фреймом 0, а затем с правильным фреймом. Если вы поместите вызов layoutIfNeeded() в layoutSubviews(), значения радиуса угла будут установлены только один раз в правильные значения. Оба заканчиваются в одном и том же месте.

vacawama 05.07.2019 14:04

В реальном случае я также видел, что вызов layoutIfNeeded() в setupViews() не даст желаемого эффекта. Так что, похоже, его следует вызывать в самом подпредставлении в layoutSubviews(), как в основном говорится в вашем ответе. Спасибо еще раз!

Felix Lieb 05.07.2019 14:07

Вы можете получить желаемый результат, если попытаетесь получить доступ к UIView.bounds из основной очереди.

override func layoutSubviews() {
    super.layoutSubviews()

    DispatchQueue.main.async {
        self.firstLevelSubview.layer.cornerRadius = self.bounds.width / 2
        self.secondLevelSubview.layer.cornerRadius = self.secondLevelSubview.bounds.width / 2
        print("bounds.width: \(self.bounds.width), contentView.bounds.width: \(self.firstLevelSubview.bounds.width), backgroundView.bounds.width: \(self.secondLevelSubview.bounds.width)")
    }
}

//print:
//bounds.width: 64.0, contentView.bounds.width: 64.0, backgroundView.bounds.width: 54.0

Но почему? Вот я и копаюсь в вашей проблеме. Вот иерархия вызовов вашего метода DemoView layoutSubviews

DemoView
|
|___layoutSubviews
    |
    |___firstLevelSubview
        |
        |___layoutSubviews //Here you are trying to access bounds of second view which is still need to be resize or achieve it's bound
            |
            |___ secondLevelSubview
                | 
                |___layoutSubviews

И поэтому в этом случае основная очередь поможет вам получить фактические границы любого UIView.subView.

В другом случае я это сделал, я пытаюсь добавить secondLevelSubview к самому DemoView с ограничениями firstLevelSubview, как показано ниже:

fileprivate func setupViews() {
    backgroundColor = UIColor.clear

    firstLevelSubview.translatesAutoresizingMaskIntoConstraints = false
    firstLevelSubview.layer.masksToBounds = true
    firstLevelSubview.backgroundColor = UIColor.green
    addSubview(firstLevelSubview)
    firstLevelSubview.topAnchor.constraint(equalTo: topAnchor).isActive = true
    firstLevelSubview.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
    firstLevelSubview.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
    firstLevelSubview.leftAnchor.constraint(equalTo: leftAnchor).isActive = true

    secondLevelSubview.translatesAutoresizingMaskIntoConstraints = false
    secondLevelSubview.layer.masksToBounds = true
    secondLevelSubview.backgroundColor = UIColor.magenta
//  firstLevelSubview.addSubview(secondLevelSubview)
    addSubview(secondLevelSubview) //Here
    secondLevelSubview.widthAnchor.constraint(equalTo: firstLevelSubview.widthAnchor, multiplier: 0.84).isActive = true
    secondLevelSubview.heightAnchor.constraint(equalTo: firstLevelSubview.heightAnchor, multiplier: 0.84).isActive = true
    secondLevelSubview.centerXAnchor.constraint(equalTo: firstLevelSubview.centerXAnchor).isActive = true
    secondLevelSubview.centerYAnchor.constraint(equalTo: firstLevelSubview.centerYAnchor).isActive = true
}

И иерархия следует, как показано ниже:

DemoView
    |
    |___layoutSubviews
        |
        |___firstLevelSubview
            |
            |___layoutSubviews
            |
            |___secondLevelSubview
            |
            |___layoutSubviews

Итак, в приведенном выше случае, если вы попытаетесь напечатать UIView.bounds без какой-либо основной очереди, вы получите желаемый результат.

Дайте мне знать, это поможет вам понять иерархию.

Другие вопросы по теме