Какова связь между UIView setNeedsLayout, layoutIfNeeded и layoutSubviews?
Может ли кто-нибудь дать окончательное объяснение взаимосвязи между методами UIView's
setNeedsLayout
, layoutIfNeeded
и layoutSubviews
? И пример реализации, где все три будут использоваться. Спасибо.
Что меня пугает, так это то, что если я отправлю свое собственное представление setNeedsLayout
сообщение, то самое следующее, которое он вызывает после того, как этот метод будет layoutSubviews
, пропуская прямо над layoutIfNeeded
. Из документов я ожидаю, что поток будет setNeedsLayout
> вызывает layoutIfNeeded
для вызовa > вызывает layoutSubviews
.
Ответы
Ответ 1
Я все еще пытаюсь понять это сам, поэтому возьмите это с некоторым скептицизмом и простите меня, если он содержит ошибки.
setNeedsLayout
является простым: он просто устанавливает флаг где-то в UIView, который маркирует его как необходимый макет. Это заставит layoutSubviews
вызываться в представлении до того, как произойдет следующее перерисовывание. Обратите внимание, что во многих случаях вам не нужно вызывать это явно из-за свойства autoresizesSubviews
. Если этот набор (который он по умолчанию), то любое изменение в кадре представления приведет к тому, что представление выведет его подпрограммы.
layoutSubviews
- это метод, в котором вы делаете все интересное. Это эквивалентно drawRect
для макета, если хотите. Тривиальный пример может быть:
-(void)layoutSubviews {
// Child frame is always equal to our bounds inset by 8px
self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
// It seems likely that this is incorrect:
// [self.subview1 layoutSubviews];
// ... and this is correct:
[self.subview1 setNeedsLayout];
// but I don't claim to know definitively.
}
AFAIK layoutIfNeeded
обычно не переопределяется в вашем подклассе. Это метод, который вы хотите вызвать, когда хотите, чтобы представление было выложено прямо сейчас. Реализация Apple может выглядеть примерно так:
-(void)layoutIfNeeded {
if (self._needsLayout) {
UIView *sv = self.superview;
if (sv._needsLayout) {
[sv layoutIfNeeded];
} else {
[self layoutSubviews];
}
}
}
Вы могли бы называть layoutIfNeeded
в представлении, чтобы принудительно вывести его (и его наблюдатели по мере необходимости).
Ответ 2
Я хотел бы добавить ответ n8gray, что в некоторых случаях вам нужно будет позвонить setNeedsLayout
, а затем layoutIfNeeded
.
Скажем, например, что вы написали пользовательское представление, расширяющее UIView, в котором позиционирование подсмотров является сложным и не может быть выполнено с помощью autoresizingMask или iOS6 AutoLayout. Пользовательское позиционирование может быть выполнено путем переопределения layoutSubviews
.
В качестве примера предположим, что у вас есть настраиваемое представление, имеющее свойство contentView
и свойство edgeInsets
, которое позволяет устанавливать поля вокруг contentView. layoutSubviews
будет выглядеть так:
- (void) layoutSubviews {
self.contentView.frame = CGRectMake(
self.bounds.origin.x + self.edgeInsets.left,
self.bounds.origin.y + self.edgeInsets.top,
self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom);
}
Если вы хотите, чтобы анимация смены кадра изменялась при изменении свойства edgeInsets
, вам необходимо переопределить установщик edgeInsets
следующим образом и вызвать setNeedsLayout
, а затем layoutIfNeeded
:
- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
_edgeInsets = edgeInsets;
[self setNeedsLayout]; //Indicates that the view needs to be laid out
//at next update or at next call of layoutIfNeeded,
//whichever comes first
[self layoutIfNeeded]; //Calls layoutSubviews if flag is set
}
Таким образом, если вы сделаете следующее, если вы измените свойство edgeInsets внутри блока анимации, будет изменена смена фрейма contentView.
[UIView animateWithDuration:2 animations:^{
customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];
Если вы не добавляете вызов layoutIfNeeded в методе setEdgeInsets, анимация не будет работать, потому что layoutSubviews будет вызван в следующем цикле обновления, что равнозначно вызову его вне блока анимации.
Если вы вызываете только layoutIfNeeded в методе setEdgeInsets, ничего не произойдет, поскольку флаг setNeedsLayout не установлен.