Коли називається layoutSubviews?


264

У мене є власний перегляд, який не отримує layoutSubviewповідомлення під час анімації.

У мене є вид, який заповнює екран. У нижній частині екрана є спеціальний підвід, який правильно змінює розмір у програмі Interface Builder, якщо я зміню висоту панелі навігації. layoutSubviewsназивається, коли створений вид, але ніколи більше. Мої підгляди правильно викладені. Якщо я відключую панель стану дзвінка, вигляд субпідключення layoutSubviewsвзагалі не викликається, навіть якщо основний вид анімує його розмір.

За яких обставин layoutSubviewsнасправді називається?

Я autoresizesSubviewsвстановив NOдля власного перегляду. А в Interface Builder у мене верхні та нижні підкоси та вертикальна стрілка.


Інша частина головоломки полягає в тому, що вікно потрібно зробити ключем:

[window makeKeyAndVisible];

з іншого боку, субперегляди не змінюються автоматично.

Відповіді:


492

У мене було подібне запитання, але я не був задоволений відповіддю (або будь-яку, яку я міг знайти в мережі), тому я спробував це на практиці, і ось що я отримав:

  • initне викликає layoutSubviewsвиклик (duh)
  • addSubview:викликає layoutSubviewsвиклик доданого перегляду, подання, до якого додається (цільовий вигляд), і всіх підпереглядів цілі
  • view setFrame інтелектуально викликає layoutSubviewsвигляд, який має встановлений кадр, лише якщо параметр розміру кадру відрізняється
  • прокрутка UIScrollView викликає layoutSubviewsвиклик у scrollView та його нагляд
  • обертання пристрою викликає лише layoutSubviewбатьківський вигляд (первинний вигляд відповідаючого контролера)
  • Змінення розміру перегляду layoutSubviewsзажадає його перегляду

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


1
Чудова відповідь. Я завжди цікавився layoutSubviews. Чи initWithFrame:викликає викликати layoutSubviews?
Роберт

2
@Robert - я використовував initWithFrame ... так що ні.
BadPirate

8
@BadPirate: так . За моїми експериментів, при зміні розмірів view1.1він викликає layoutSubviewsз , view1а потім layoutSubviewsз view1.1. Цей виклик не розповсюджується на невизначений термін на перегляди, називаючи його view1.1.1лише дзвінками layoutSubviewsна view1.1та view1.1.1. Просто переміщення, не змінюючи розмір, не вимагає layoutSubviewsжодної з них.
Жоао Портела

1
viewDidLoad не викликається в UIView (але UIViewController). Перегляд Чи завантажувалося завантаження після того, як UIView буде ініційовано.
BadPirate

2
Грунтуючись на моєму досвіді, друге правило може бути не точним: коли я додаю view1.2в view1, layoutSubviewsз view1.2і view1називаються, але layoutSubviewsз view1.1не називається. ( view1.1і view1.2є підзаглядом view1). Тобто, не всі підгляди цільового виду називаються layoutSubviewsметодом .
HongchaoZhang

96

Спираючись на попередню відповідь @BadPirate, я трохи далі експериментував і придумав деякі пояснення / виправлення. Я виявив, що layoutSubviews:буде викликано в представленні, якщо і лише якщо:

  • Змінилися власні межі (не кадр).
  • Межі одного з його прямих підглядів змінилися.
  • Підперегляд додається до перегляду або видаляється з нього.

Деякі відповідні деталі:

  • Межі вважаються зміненими лише в тому випадку, якщо нове значення відрізняється, включаючи інше походження . Зауважте, що саме тому layoutSubviews:викликається щоразу, коли UIScrollView прокручується, оскільки він виконує прокрутку, змінюючи походження своїх меж.
  • Зміна кадру змінить межі лише в тому випадку, якщо розмір змінився, оскільки це єдине, що передається властивості меж.
  • Зміна меж зору перегляду, який ще не існує в ієрархії перегляду, призведе до виклику, layoutSubviews: коли подання з часом буде додано до ієрархії перегляду .
  • І лише для повноти: ці тригери не викликають безпосередньо layoutSubviews, а скоріше дзвінок setNeedsLayout, який встановлює / піднімає прапор. Кожна ітерація циклу запуску, для всіх переглядів в ієрархії подання , цей прапор перевіряється. Для кожного виду, де прапор виявлений піднятим, layoutSubviews:викликається ним і прапор скидається. Перегляди вище вгору за ієрархією будуть перевірені / викликані спочатку.

5
не може достатньо підтвердити цю відповідь. це має бути головна відповідь. три наведені правила - це все, що вам потрібно. я ніколи не стикався з якоюсь поведінкою layoutSubview, яку ці правила не описали ідеально.
Pärserk

1
Я не думаю, що layoutSubviews викликається при зміні меж прямого підвиду. Я думаю, що виклик layoutSubviews - лише побічний ефект вашого тестування. У деяких випадках layoutSubviews не буде викликано, коли змінити межі перегляду. Будь ласка, перевірте відповідь frogcjn, оскільки його віщун базується на документації Apple, а не просто на експериментах.
Simon Backx

19

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

Зміни макета можуть відбуватися, коли в перегляді відбувається будь-яке з наступних подій:

а. Змінюється розмір прямокутника меж перегляду.
б. Відбувається зміна орієнтації інтерфейсу, яка, як правило, викликає зміну прямокутника меж кореневого виду.
c. Набір підшарів основної анімації, пов'язаних із шаром подання, змінюється та вимагає компонування.
г. Ваша програма змушує мати розміщення за допомогою виклику  setNeedsLayout або  layoutIfNeeded методу перегляду.
е. Ваша програма змушує розміщувати виклик  setNeedsLayout методу об'єкта, що лежить в нижньому шарі.


Також важливо зазначити, що жодна з цих подій не викликається, коли подання ще не додано до стеку перегляду. Ви включаєте це зі словом "can", але конкретно воно викликається у наступному доступному циклі головного потоку програми, коли воно було позначене як потрібне розташування одним із цих подій.
user1122069

13

Деякі моменти у відповіді BadPirate лише частково вірні:

  1. Для addSubViewточки

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

    Це залежить від автоматичної розміри маски подання (цільового виду). Якщо в ньому ввімкнена маска автоматичного розміру, буде виклик layoutSubview для кожногоaddSubview . Якщо у нього немає маски автоматичного розміру, тоді layoutSubview буде викликатися лише тоді, коли розмір кадру перегляду (цільового перегляду) зміниться.

    Приклад: якщо ви створили програму UIView програмно (у неї за замовчуванням немає маски автоматичного розміру), LayoutSubview буде викликатися лише тоді, коли фрейм UIView змінюється не на всіх addSubview .

    Саме завдяки цій техніці збільшується і продуктивність програми.

  2. Для точки обертання пристрою

    Обертання пристрою викликає лише layoutSubview на батьківському поданні (первинний вигляд відповідіCoController)

    Це може бути правдою лише тоді, коли ваш ВК знаходиться в ієрархії ВК (корінь у window.rootViewController), і це найчастіше. У iOS 5, якщо ви створюєте VC, але він не додається до будь-якого іншого VC, тоді цей VC не буде помічений під час обертання пристрою. Тому його погляд не помітить, зателефонувавши layoutSubviews.


9

Я простежив рішення до наполягання Interface Builder на тому, що пружини не можна змінювати на поданні, у якому увімкнені імітовані елементи екрана (рядок стану тощо). Оскільки пружини були вимкнені для основного виду, цей вигляд не міг змінити розмір і, отже, прокрутився вниз у повному обсязі, коли з'явилася панель дзвінка.

Якщо вимкнути імітовані функції, а потім змінити розмір подання та правильно встановити пружини, це спричинило виникнення анімації та виклик мого методу.

Додатковою проблемою при налагодженні цього є те, що тренажер запускає додаток, коли стан виклику перемикається через меню. Закрити додаток = немає налагоджувача.


Ви хочете сказати, що layoutSubviews викликається під час зміни розміру представлення? Я завжди припускав, що це не так ...
Андрій Таранцов

Це є. Коли розмір перегляду буде змінено, йому потрібно щось зробити зі своїми підглядами. Якщо ви не надаєте його, то він автоматично переміщує їх за допомогою пружин, підкосів тощо.
Стів Веллер

8

виклик [self.view setNeedsLayout]; у viewController дозволяє викликати viewDidLayoutSubviews


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
Чому ця відповідь тут?
Домінік Бучер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.