Когда layoutSubviews называется?

у меня есть пользовательский вид, который не получает layoutSubview сообщения во время анимации.

у меня есть вид, который заполняет экран. В нижней части экрана есть настраиваемое подвиде, которое правильно изменяется в Interface Builder, если я изменяю высоту панели навигации. layoutSubviews вызывается при создании представления, но больше никогда. Мои подвиды правильно изложены. Если я отключу строку состояния в вызове, подвида layoutSubviews не вызывается вообще, хотя основной вид анимируется его размер.

при каких обстоятельствах layoutSubviews на самом деле называется?

Я autoresizesSubviews значение NO для моего пользовательского представления. И в Interface Builder у меня есть верхняя и нижняя стойки и набор вертикальных стрелок.

10 ответов


у меня был аналогичный вопрос, но не был удовлетворен ответом (или любым, который я мог найти в сети), поэтому я попробовал его на практике, и вот что я получил:

  • init не вызывает layoutSubviews to называться (duh)
  • addSubview: причины layoutSubviews быть вызванным на вид добавляется, вид это добавлено в (целевой вид), и все подвиды цели
  • посмотреть setFrame интеллигентно зовет layoutSubviews на вид, имеющий свой набор кадров только если параметр size кадра разные
  • прокрутка UIScrollView причины layoutSubviews для вызова scrollView и его супервизор
  • поворот всего устройства звонки layoutSubview на родительский вид (на первичный ответ viewControllers view)
  • изменение размера представления вызовет layoutSubviews на его суперпанель

мои результаты - http://blog.logichigh.com/2011/03/16/when-does-layoutsubviews-get-called/


основываясь на предыдущем ответе @BadPirate, я экспериментировал немного дальше и придумал некоторые разъяснения/исправления. Я нашел это layoutSubviews: будет вызываться на просмотр, если и только если:

  • своя границы (не рамы) изменен.
  • границы одного из его прямых подвидов изменились.
  • вложенное представление добавляется в представление или удаляется из представления.

некоторые важные детали:

  • в границы считаются измененными, только если новое значение отличается, включая другое происхождение. Обратите внимание, именно поэтому layoutSubviews: вызывается всякий раз, когда UIScrollView прокручивается, поскольку он выполняет прокрутку, изменяя происхождение своих границ.
  • изменение кадра изменит только границы, если размер изменился, так как это единственное, что распространяется на свойство bounds.
  • изменение границ представления, которое еще не находится в иерархии представлений, будет результат вызова layoutSubviews: когда представление в конечном итоге добавляется в иерархию представлений.
  • и просто для полноты: эти триггеры не напрямую вызов layoutSubviews, но скорее вызов setNeedsLayout, который устанавливает/поднимает флаг. Каждая итерация цикла выполнения для всех представлений в иерархии представлений этот флаг. Для каждого вида, где флаг найден поднятым,layoutSubviews: называется на нем и флаг сбрасывается. Мнения выше сначала будет проверена/вызвана иерархия.

https://developer.apple.com/library/prerelease/tvos/documentation/WindowsViews/Conceptual/ViewPG_iPhoneOS/CreatingViews/CreatingViews.html#//apple_ref/doc/uid/TP40009503-CH5-SW1

изменения макета могут произойти всякий раз, когда происходит одно из следующих событий в виде:

a. Размер прямоугольника границ представления изменяется.
b. Происходит изменение ориентации интерфейса, которое обычно вызывает изменение границ корневого представления прямоугольник.
c. Набор основных анимационных подслоев, связанных со слоем представления, изменяется и требует компоновки.
d. Ваше приложение заставляет макет происходить, вызывая setNeedsLayout или layoutIfNeeded метод просмотра.
e. Ваше приложение заставляет макет, вызывая setNeedsLayout метод объекта базового слоя представления.


некоторые из пунктов в ответ BadPirate верны только частично:

  1. на addSubView точка

    addSubview вызывает layoutSubviews для добавляемого представления, представления, в которое оно добавляется (целевое представление), и всех подвидов цели.

    это зависит от вида (целевой вид) autoresize маска. Если он имеет маску autoresize, layoutSubview будет вызываться на каждом addSubview. Если у него нет маска autoresize затем layoutSubview будет вызываться только при изменении размера кадра представления (целевого представления).

    пример: если вы создали UIView программно (по умолчанию он не имеет маски авторазбора), LayoutSubview будет вызываться только при изменении фрейма UIView не на каждом addSubview.

    именно благодаря этой технике производительность приложения также увеличивается.

  2. для точки вращения устройства

    поворот устройство вызывает layoutSubview только в Родительском представлении (основное представление отвечающего viewController)

    это может быть верно только тогда, когда ваш VC находится в иерархии VC (root at window.rootViewController), ну это самый распространенный случай. В iOS 5, Если вы создаете VC, но он не добавляется ни в один другой VC, то этот VC не будет замечен при вращении устройства. Поэтому его вид не будет замечен при вызове layoutSubviews.


вызов [self.view setNeedsLayout]; в viewController позволяет вызывать viewDidLayoutSubviews


я отследил решение до настояния Interface Builder на том, что пружины не могут быть изменены в представлении, в котором включены имитируемые элементы экрана (строка состояния и т. д.). Поскольку пружины были отключены для основного вида, этот вид не мог изменить размер и, следовательно, был прокручен вниз полностью, когда появилась панель вызова.

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

дополнительная проблема в отладке заключается в том, что симулятор выходит из приложения, когда статус вызова переключается через меню. Выход из приложения = нет отладчика.


вы смотрели на layoutIfNeeded?

ниже приведен фрагмент документации. Работает ли анимация, если вы вызываете этот метод явно во время анимации?

layoutIfNeeded При необходимости выкладывает подвиды.

- (void)layoutIfNeeded

Обсуждение Используйте этот метод для принудительной компоновки подвидов перед рисованием.

в наличии Доступно в iPhone OS 2.0 и более поздних версиях.


при переносе приложения OpenGL из SDK 3 в 4 layoutSubviews больше не вызывался. После многих проб и ошибок я, наконец, открыл MainWindow.xib, выделил объект Window, в инспекторе выбрал вкладку window Attributes (крайняя слева) и проверил "видимый при запуске". Кажется, что в SDK 3 он все еще использовался для вызова layoutSubViews, но не в 4.

6 часов разочарования положить конец.


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

[window makeKeyAndVisible];

of else подвиды не изменяются автоматически.


довольно неясный, но потенциально важный случай, когда layoutSubviews никогда не называли это:

import UIKit

class View: UIView {

    override class var layerClass: AnyClass { return Layer.self }

    class Layer: CALayer {
        override func layoutSublayers() {
            // if we don't call super.layoutSublayers()...
            print(type(of: self), #function)
        }
    }

    override func layoutSubviews() {
        // ... this method never gets called by the OS!
        print(type(of: self), #function)
    }
}

let view = View(frame: CGRect(x: 0, y: 0, width: 100, height: 100))