Какова взаимосвязь между setNeedsLayout, layoutIfNeeded и layoutSubviews в UIView?

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

Что меня смущает, так это то, что если я отправляю своему пользовательскому представлению сообщение setNeedsLayout, то следующее, что оно вызывает после того, как этот метод ---- +: = 5 =: + ----, пропуская сразу layoutSubviews. Из документов я ожидаю, что поток будет layoutIfNeeded> вызывает setNeedsLayout для вызова> вызывает layoutIfNeeded.

104 голоса | спросил Tarfa 11 Mayam10 2010, 03:31:14

2 ответа


0

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

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.
}

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

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

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

ответил n8gray 28 Maypm10 2010, 22:54:02
0

Я хотел бы добавить к ответу 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 не установлен.

ответил quentinadam 20 ThuEurope/Moscow2012-12-20T03:15:26+04:00Europe/Moscow12bEurope/MoscowThu, 20 Dec 2012 03:15:26 +0400 2012, 03:15:26

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132