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

264

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

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

При каких обстоятельствах это layoutSubviewsфактически называется?

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


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

[window makeKeyAndVisible];

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

Стив Веллер
источник

Ответы:

492

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

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

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

BadPirate
источник
1
Отличный ответ. Я всегда задавался вопросом о layoutSubviews. Есть ли initWithFrame:причина layoutSubviewsдля вызова?
Роберт
2
@ Роберт - я использовал initWithFrame ... так что нет.
BadPirate
8
@BadPirate: да . Согласно моим экспериментам, если вы измените размер, view1.1то вызовы layoutSubviewsиз, view1а затем layoutSubviewsиз view1.1. Этот вызов не распространяется на неопределенный срок до superviews, называя его view1.1.1только звонки layoutSubviewsна view1.1и view1.1.1. Просто движение без изменения его размера не вызывает layoutSubviewsни одного из них.
Жоао Портела
1
viewDidLoad не вызывается в UIView (но UIViewController). View Загружается ли вызывается после инициализации UIView.
BadPirate
2
Исходя из моего эксперимента, второе правило может быть не точным: когда я добавляю view1.2в view1, layoutSubviewsиз view1.2и view1вызываются, но layoutSubviewsиз view1.1не вызывается. ( view1.1и view1.2являются подпредставлениями view1). То есть не все подпредставления целевого представления называются layoutSubviewsметодом .
Хунчао Чжан,
96

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

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

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

  • Границы считаются измененными, только если новое значение отличается, включая другое происхождение . Обратите особое внимание на то, что именно поэтому он layoutSubviews:вызывается всякий раз, когда UIScrollView прокручивается, поскольку он выполняет прокрутку, изменяя источник своих границ.
  • Изменение фрейма изменит границы только в том случае, если изменился размер, так как это единственное, что распространяется на свойство bounds.
  • Изменение границ представления, которое еще не находится в иерархии представления, приведет к вызову layoutSubviews: когда представление в конечном итоге будет добавлено в иерархию представления .
  • И просто для полноты: эти триггеры напрямую не вызывают layoutSubviews, а скорее вызывают setNeedsLayout, который устанавливает / поднимает флаг. На каждой итерации цикла выполнения для всех представлений в иерархии представлений этот флаг проверяется. Для каждого просмотра, где флаг поднят, layoutSubviews:вызывается и флаг сбрасывается. Представления выше по иерархии будут проверены / вызваны первыми.
Патрик Пийнаппель
источник
5
не могу высказать этот ответ достаточно. это должен быть главный ответ. три правила, это все, что вам нужно. я никогда не сталкивался с поведением layoutSubview, которое эти правила не описывали идеально.
Pärserk
1
Я не думаю, что layoutSubviews вызывается, когда изменяются границы прямого подпредставления. Я думаю, что вызов layoutSubviews - это просто побочный эффект вашего тестирования. В некоторых случаях layoutSubviews не будет вызываться при изменении границ подвидов. Пожалуйста, проверьте ответ frogcjn, потому что его ответ основан на документации Apple, а не на экспериментах.
Саймон Бекс
19

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

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

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

frogcjn
источник
Также важно отметить, что ни одно из этих событий не вызывается, когда представление еще не добавлено в стек представления. Вы включаете это в слово «can», но, в частности, оно вызывается в следующем доступном цикле основного потока приложения, когда оно было помечено как нужное расположение одним из этих событий.
user1122069
13

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

  1. Для addSubViewточки

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

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

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

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

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

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

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

Мохит Нигам
источник
9

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

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

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

Стив Веллер
источник
Вы говорите, что layoutSubviews вызывается при изменении размера представления? Я всегда предполагал, что это не так ...
Андрей Таранцов
Это. Когда размер представления изменяется, он должен что-то делать со своими подпредставлениями. Если вы не предоставите его, он автоматически перемещает их с помощью пружин, распорок и т. Д.
Стив Уэллер,
8

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

bademi
источник
5

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

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

layoutIfNeeded Выкладывает подпредставления, если это необходимо.

- (void)layoutIfNeeded

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

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

Вилли Баллентхин
источник
2

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

6 часов разочарования подошли к концу.

Таль Янив
источник
Вы сделали ключ окна? Если нет, это может привести к тому, что всякого рода интересных вещей не произойдет.
Стив Веллер
-2

Довольно неясный, но потенциально важный случай, когда 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))
Милош
источник
1
Почему этот ответ здесь?
Доминик Бухер,