Границы подпредставления пользовательского UIView кажутся равными 0 в layoutSubviews(), поэтому использование границ в layoutSubviews() является проблемой.
Чтобы показать проблему, я выложил демо на GitHub: SOBoundsAreZero
Вот прямая ссылка на реализацию пользовательского представления: DemoView.swift
Структура пользовательского представления "DemoView" такова:
DemoView
firstLevelSubview
secondLevelSubview
Эта структура создается программно с использованием Auto Layout.
Когда вызывается layoutSubviews(), представление и firstLevelSubview имеют ожидаемые границы, но границы secondLevelSubview равны 0.
Я ожидал, что все подпредставления, использующие Auto Layout, будут иметь правильные границы, по крайней мере, в последнем вызове layoutSubviews.
Структура является абстракцией реального случая. Чтобы избежать этой проблемы, secondLevelSubview можно добавить в качестве подпредставления первого уровня в DemoView. Хотя, это то, что невозможно в реальном случае.
Я чувствую, что мне здесь не хватает чего-то простого, даже если это ожидаемое поведение.
@Vicaren, да, все готово. Я добавил ссылку на реализацию для удобства.





Во-первых, ваше представление интерпретируется без проблем, потому что у вас есть инициализатор, основанный на «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() в функции, которая устанавливает ограничения, после этого имеет смысл для меня. Я думаю, что это была «простая вещь», которую я пропустил.
Интересно, что если вы позвоните layoutIfNeeded() в setupViews(), layoutSubviews() будет вызвана дважды, сначала с фреймом 0, а затем с правильным фреймом. Если вы поместите вызов layoutIfNeeded() в layoutSubviews(), значения радиуса угла будут установлены только один раз в правильные значения. Оба заканчиваются в одном и том же месте.
В реальном случае я также видел, что вызов layoutIfNeeded() в setupViews() не даст желаемого эффекта. Так что, похоже, его следует вызывать в самом подпредставлении в layoutSubviews(), как в основном говорится в вашем ответе. Спасибо еще раз!
Вы можете получить желаемый результат, если попытаетесь получить доступ к 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 без какой-либо основной очереди, вы получите желаемый результат.
Дайте мне знать, это поможет вам понять иерархию.
Вы добавили ограничение и установили translatesAutoresizingMaskIntoConstraints = false?