setNeedsLayout против setNeedsUpdateConstraints и layoutIfNeeded против updateConstraintsIfNeeded

227

Я знаю, что цепочка автоматической разметки состоит в основном из 3 различных процессов.

  1. Обновление ограничений
  2. макеты просмотров (вот где мы получаем расчет кадров)
  3. дисплей

Что мне не совсем понятно, так это внутренняя разница между -setNeedsLayoutи -setNeedsUpdateConstraints. Из Apple Docs:

setNeedsLayout

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

setNeedsUpdateConstraints

Когда свойство вашего пользовательского представления изменяется таким образом, что это повлияет на ограничения, вы можете вызвать этот метод, чтобы указать, что ограничения должны быть обновлены в какой-то момент в будущем. Затем система вызовет updateConstraints как часть обычного прохода макета. Одновременное обновление ограничений непосредственно перед тем, как они понадобятся, гарантирует, что вы не будете без необходимости пересчитывать ограничения, когда в ваши представления вносятся множественные изменения между проходами макета.

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

[UIView animateWithDuration:1.0f delay:0.0f usingSpringWithDamping:0.5f initialSpringVelocity:1 options:UIViewAnimationOptionCurveEaseInOut animations:^{
        [self.modifConstrView setNeedsUpdateConstraints];
        [self.modifConstrView layoutIfNeeded];
    } completion:NULL];

Я обнаружил, что если я использую -setNeedsLayoutвместо того, чтобы -setNeedsUpdateConstraintsвсе работало, как ожидалось, но если я изменился -layoutIfNeededс -updateConstraintsIfNeeded, анимация не произойдет.
Я попытался сделать свой собственный вывод:

  • -updateConstraintsIfNeeded только обновляет ограничения, но не вынуждает макет войти в процесс, таким образом, оригинальные кадры все еще сохраняются
  • -setNeedsLayoutвызывает также -updateContraintsметод

Итак, когда можно использовать один вместо другого? и о методах макета, нужно ли мне вызывать их в представлении, которое имеет изменение в ограничении, или в родительском представлении?

Andrea
источник
27
Я не понимаю, что люди голосуют ... правда. Так что вы должны что-то с этим сделать, например, задать причину как обязательную, или они совершенно бессмысленны
Андреа
7
Возможно, им просто нужно получить значок «Критик» (первый голос «за»)
fujianjin6471,
1
Я настоятельно рекомендую вам посмотреть здесь . Ответ - скорее решение реальной проблемы. Также смотрите это видео
Honey

Ответы:

258

Ваши выводы верны. Основная схема:

  • setNeedsUpdateConstraintsудостоверится, что будущий звонок на updateConstraintsIfNeededзвонки updateConstraints.
  • setNeedsLayoutудостоверится, что будущий звонок на layoutIfNeededзвонки layoutSubviews.

Когда layoutSubviewsвызывается, он также вызывает updateConstraintsIfNeeded, поэтому, по моему опыту, вызов этого вручную редко требуется. На самом деле, я никогда не вызывал его, за исключением случаев отладки макетов.

Обновление ограничений с использованием setNeedsUpdateConstraintsдовольно редко, objc.io - обязательное чтение об автопоставках - говорит :

Если позже что-то изменится, что сделает недействительным одно из ваших ограничений, вы должны немедленно удалить ограничение и вызвать setNeedsUpdateConstraints. Фактически, это единственный случай, когда вам нужно запустить этап обновления ограничения.

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

Эмпирические правила:

  • Если вы манипулировали ограничениями напрямую, звоните setNeedsLayout.
  • Если вы изменили некоторые условия (например, смещения или что-л.), Которые могли бы изменить ограничения в вашем переопределенном updateConstraintsметоде (рекомендуемый способ изменить ограничения, кстати), вызов setNeedsUpdateConstraintsи большую часть времени setNeedsLayoutпосле этого.
  • Если вам нужно, чтобы какое-либо из указанных выше действий имело немедленный эффект - например, когда вам нужно узнать новую высоту кадра после прохода макета - добавьте его с помощью layoutIfNeeded.

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

coverback
источник
@coverback, поэтому objc.io говорит: «Если позже что-то изменится и сделает недействительным одно из ваших ограничений, вы должны немедленно удалить ограничение и вызвать setNeedsUpdateConstraints. Фактически, это единственный случай, когда вам нужно инициировать этап обновления ограничения». И затем в блоке Анимация говорит, что когда я удаляю, добавляю или изменяю constraint.contant, мне нужно вызвать setNeedsLayout. Какая разница? Я чувствую себя очень глупо :(
pash3r
3
@ pash3r Разница в том, что обновление константы не квалифицируется как «аннулирование». Аннулирование - это когда оно больше не актуально, например, должно быть присоединено к другому представлению или полностью удалено. Константа просто поместит взгляд ближе или дальше, или изменит его размер, таким образом, необходимость setNeedsLayout.
обложка
@coverback setNeedsLayoutгарантирует, что layoutSubviewsбудет вызван в следующем цикле обновления, но, возможно, это не имеет ничего общего с layoutIfNeeded?
fujianjin6471
2
@coverback Если вы манипулируете ограничениями напрямую, layoutSubviewsбудет вызываться автоматически, звонить не нужноsetNeedsLayout
fujianjin6471
Да, манипулирование свойствами ограничения напрямую сработает layoutSubviews, поэтому нет необходимости делать это вручную. Однако layoutIfNeededвам нужно позвонить, если вам нужно, чтобы изменения вступили в силу немедленно, а не в следующем цикле макета
Чарли Мартин,
89

Ответ на coverback довольно правильно. Тем не менее, я хотел бы добавить некоторые дополнительные детали.

Ниже приведена схема типичного цикла UIView, который объясняет другое поведение:

Жизненный цикл UIView

  1. Я обнаружил, что если я использую -setNeedsLayoutвместо того, чтобы -setNeedsUpdateConstraintsвсе работало, как ожидалось, но если я изменился -layoutIfNeededс -updateConstraintsIfNeeded, анимация не произойдет.

updateConstraintsобычно ничего не делает Он просто разрешает ограничения, но не вызывает их, пока не layoutSubviewsбудет вызван. Так что анимация требует вызова layoutSubviews.

  1. setNeedsLayout также вызывает метод -updateContraints

Нет, это не обязательно. Если ваши ограничения не были изменены, UIView пропустит вызов updateConstraints. Вам нужно явно позвонить, setNeedsUpdateConstraintчтобы изменить ограничения в процессе.

Для звонка updateConstraintsнеобходимо сделать следующее:

[view setNeedsUpdateConstraints];
[view setNeedsLayout]; 
[view layoutIfNeeded];
Кунал Балани
источник
Спасибо, это решило мою проблему. У меня было UIWindow без родительского UIView, в котором были добавлены временные ограничения при вызове LayoutIfNeeded () перед анимацией. Добавление обёртки подпредставления в окно UIWindow и вызов этих трех методов исправило мою проблему.
masterwok
Я не думаю, что вызов layoutIfNeeded сразу после setNeedsLayout является правильным. Потому что методы делают то же самое, несмотря на то, что один вызывает немедленное перерисовывание макета, а второй - в следующем цикле обновления.
fillky