Какова взаимосвязь между setNeedsLayout, layoutIfNeeded и layoutSubviews в UIView? - PullRequest
105 голосов
/ 11 мая 2010

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

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

Ответы [ 2 ]

103 голосов
/ 28 мая 2010

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

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, чтобы заставить его (и его суперпредставления при необходимости) быть немедленно выложенным.

34 голосов
/ 20 декабря 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...