Какая связь между установками UIViewNeedsLayout, layoutIfNeeded и layoutSubviews?

Может ли кто-нибудь дать окончательное объяснение отношения между установками UIView's setNeedsLayout , layoutIfNeeded и layoutSubviews ? И пример реализации, где все три будут использоваться. Благодарю.

Меня смущает то, что если я отправлю свое пользовательское представление в сообщение setNeedsLayout то самое следующее, что он вызывает после того, как этот метод будет layoutSubviews , пропуская прямо над layoutIfNeeded . Из документов я ожидал бы, что stream будет setNeedsLayout > вызывает layoutIfNeeded который вызывается> вызывает layoutSubviews .

Я все еще пытаюсь понять это сам, поэтому возьмите это с некоторым скептицизмом и простите меня, если он содержит ошибки.

setNeedsLayout – простой: он просто устанавливает флаг где-то в UIView, который отмечает его как необходимый макет. Это заставит layoutSubviews вызываться в представлении до того, как произойдет следующее перерисовывание. Обратите внимание, что во многих случаях вам не нужно вызывать это явно из-за свойства autoresizesSubviews . Если это установлено (что по умолчанию), то любое изменение в кадре представления приведет к тому, что представление выведет его подпрограммы.

layoutSubviews – это метод, в котором вы делаете все интересное. Это эквивалент drawRect для макета, если хотите. Тривиальный пример:

 -(void)layoutSubviews { // Child's 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. } 

layoutIfNeeded обычно не предназначается для переопределения в вашем подclassе. Это метод, который вы должны назвать, когда хотите, чтобы представление было выложено прямо сейчас . Реализация Apple может выглядеть примерно так:

 -(void)layoutIfNeeded { if (self._needsLayout) { UIView *sv = self.superview; if (sv._needsLayout) { [sv layoutIfNeeded]; } else { [self layoutSubviews]; } } } 

Вы бы назвали layoutIfNeeded в представлении, чтобы заставить его (и его наблюдатели по мере необходимости) немедленно выкладываться.

Я хотел бы добавить ответ n8gray, что в некоторых случаях вам нужно будет вызвать setNeedsLayout за которым следует layoutIfNeeded .

Скажем, например, что вы написали пользовательское представление, расширяющее UIView, в котором позиционирование подзонов является сложным и не может быть выполнено с помощью autoresizingMask или iOS6 AutoLayout. Пользовательское позиционирование может быть выполнено путем переопределения layoutSubviews .

В качестве примера предположим, что у вас есть пользовательское представление с свойством edgeInsets свойством 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 , вам необходимо переопределить 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 не установлен.

  • Простое обнаружение движения iPhone
  • Как получить размер NSString
  • Как установить include path в проект xcode
  • Есть ли способ для Interface Builder визуализировать представления IBDesignable, которые не переопределяют drawRect:
  • Трассировка стека или дополнительная информация о необработанном исключении в Xcode / iPhone
  • Xcode 6 / Beta 4: использование заголовков мостов с объектами инфраструктуры не поддерживается
  • Как ввести значения RGB в Interface Builder?
  • Недопустимые права
  • Как избежать ошибок «дублирования символа» в xcode с помощью общих статических библиотек?
  • Приложение Xcode 4.6.2 сбой при каждом втором запуске
  • Ошибка кодового знака в macOS High Sierra Xcode - ресурс fork, информация Finder или подобный детрит не разрешены
  • Давайте будем гением компьютера.