Многие iOS разработчики не задумываются как работает механизм отрисовки элементов, установки и обновлении constraints в Auto Layout'e. В этой статье я пробую подробно заглянуть внутрь работы Layout Engine

The Layout Pass

The Layout Pass - это когда Auto Layout Engine обходит иерархию представлений, но вызывает метод ViewWillLayoutSubviews на всех viewController и метод layoutSubviews для всех view. Метод layoutSubviews обновляет свойство Frame каждого subview

Когда мы добавляем, изменяем или удаляем constraint'ы, то они не обновляются сразу. Пересчет layout'a и обновление отрисовки при каждом изменении было бы неэффективным. Вместо этого ваши изменения планируют, что механизм layout'a, принадлежащий window, запускает обновления layout'a при следующей возможности в runloop'e.

Типичный layout cycle состоит из нескольких этапов:

  1. Trigger: Вы меняете input в layout engine. Это может быть добавление или удаление subviews, изменение внутреннего размера контента, активация / деактивация contstraints или изменение приоритета или постоянного значения constraint.

  2. Update Model: layout engine имеет внутреннюю модель размера и положения каждого view и уравнения, описывающие отношения между этими views. При изменении входных данных the layout engine обновляет свою внутреннюю модель и решает уравнения для получения новых значений размера и положения каждого view.

    На данный момент изменилась только внутренняя модель. Views, которые теперь имеют новый размер или положение в модели, вызывают setNeedsLayout() в своем superview, который планирует отложенный layout pass для запуска через некоторое время в цикле runloop.

  3. Deferred Layout Pass: Когда выполняется the layout pass он запускает два прохода по view hierarchy. Первый проход дает вам последнюю возможность обновить любые constraints. Второй проход, наконец, вызывает layoutSubviews() для каждого view, позволяя им обновлять размер и положение своих subviews, чтобы они соответствовали внутренней модели, перемещая любые views по мере необходимости.

Есть несколько методов, которые вы можете переопределить как во viewController, так и во view для взаимодействия с layout engine во время двух проходов компоновки:

  • При обновлении констрейнт проход layout engine вызывает метод вьюконтроллера updateViewConstraints, а для view ожидает обновление метода updateConstraints.

  • the layout pass подает сигнал layout engine и он вызывает viewWillLayoutSubviews и viewDidLayoutSubviews методы, а также layoutSubviews всех вью нуждающих в обновлении layout

Обновление Constraints

Первый проход по view hierarchy осуществляется снизу вверх, чтобы вы могли изменить constraint'ы до того, как layout engine изменит положение views. Механизм компоновки вызывает updateConstraintsIfNeeded для каждого view, чтобы проверить наличие последних constraints. Для view, помеченных как требующие обновления своих constraints, layout engine вызывает их метод updateConstraints, в котором вы можете внести изменения. Вызовите setNeedsUpdateConstraints, чтобы запросить передачу ограничений обновления для представления. Если вы хотите, чтобы the layout engine обновлял свою модель, немедленно вызовите метод updateConstraintsIfNeeded.

Репозиция Views

Второй проход по иерархии представлений - это переход сверху вниз для репозиции view. Только во время этого прохода the layout engine обновляет view frames в соответствии со своей внутренней моделью.

Для каждого представления, требующего layout, поведение layoutSubviews () по умолчанию устанавливает границы и центр каждого subviews на новые значения из модели layout engine.

Вызовите setNeedsLayout или layoutIfNeeded, чтобы запросить layout pass для view. Ключевое различие между этими методами заключается в том, что layout pass обновляет view вот так:

  • setNeedsLayout: немедленно возвращается без обновления layout. Вместо этого он отмечает layout во view как измененный и планирует отложенный layout pass для запуска в application run loop.

  • layoutIfNeeded: вызывает layoutSubviews на приемнике, если есть ожидающие изменения, чтобы заставить layout engine немедленно обновить размер и положение subviews из своей внутренней модели

Выводы:

  • Активация, изменение или деактивация constraints не обновляет напрямую фреймы вьюх. Вместо этого он обновляет модель the layout engine и планирует передачу макета, которая выполняется позже в цикле выполнения приложения.

  • В иерархии представлений есть два прохода макета. Первый проход позволяет вам обновить constraints. Второй проход обновляет view layout, изменяя размер и положение представлений, чтобы они соответствовали значениям из модели the layout engine.

  • Вызовите setNeedsLayout, чтобы вручную запланировать этап обновления layout'a. Вызов layoutIfNeeded для принудительного немедленного обновления view frames из модели.