Яка взаємозв'язок між набором UIView setNeedsLayout, layoutIfNeeded та layoutSubviews?


105

Може хто - небудь дати однозначне пояснення про взаємозв'язок між UIView's setNeedsLayout, layoutIfNeededі layoutSubviewsметодах? І приклад реалізації, де будуть використані всі три. Дякую.

Мене бентежить те, що якщо я надсилаю своє власне подання setNeedsLayoutповідомлення, саме наступне, на що він звертається після цього методу, - layoutSubviewsпропускаючи прямо layoutIfNeeded. Від документів я б очікував, що потік буде setNeedsLayout> причини layoutIfNeededвикликати> причини layoutSubviewsвикликати.

Відповіді:


104

Я все ще намагаюся розібратися в цьому, тому сприйміть це з деяким скептицизмом і пробачте мене, якщо воно містить помилки.

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Як правило, AFAIK не може бути переосмислений у вашому підкласі. Це метод, який ви повинні зателефонувати, коли ви хочете, щоб представлення було викладено прямо зараз . Реалізація Apple може виглядати приблизно так:

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

Ви б закликали layoutIfNeededпогляд, щоб змусити його (і його нагляди, якщо це необхідно) негайно викласти.


2
Дякую - радий, що нарешті хтось відповів на це. Тим часом мені також довелося заглибитись у setNeedsDisplay - що викликає виклик drawRect - та contentMode. Тільки contentMode = UIViewContentModeRedraw призведе до виклику setNeedsDisplay при зміні меж перегляду. Інші варіанти contentMode лише спричиняють масштабування подання, зміщення на деяку кількість або вирівнювання до краю. Мій користувальницький UITableViewCell не хотів перемальовувати зміни орієнтації, він буде лише масштабуватись, поки я не встановлю для UMiewContentModeRedraw contentMode.
Тарфа

Я думаю, можливо, [self.subview1 layoutSubviews] у вашому коді слід замінити на [self.subview1 setNeedsLayout]. Оскільки layoutSubviews призначений для перевизначення, але не призначений для виклику з іншого або іншого представлення. Або фреймворк визначає, коли йому зателефонувати (об'єднавши кілька запитів на макет в один виклик для ефективності), або ви опосередковано зробите виклик layoutIfNeeded десь в ієрархії перегляду.
Тарфа

Виправлення на мій вище коментар: "... Оскільки layoutSubviews призначений для зміни або виклику від себе, але не призначений для виклику з іншого або іншого виду ..."
Тарфа,

Я справді не впевнений у цьому [subview1 layoutSubviews]. Цілком може бути так, що, начебто drawRect, ви не повинні називати це безпосередньо. Але я не впевнений, що дзвінок setNeedsLayoutпід час фази компонування призведе до того, що подання буде викладене під час тієї ж фази компонування або якщо воно затримане до наступного. Було б добре побачити відповідь від того, хто справді розуміє, як це все працює ...
n8gray

34

Я хотів би додати у відповідь n8gray, що в деяких випадках вам потрібно буде зателефонувати, setNeedsLayoutпісля чого layoutIfNeeded.

Скажімо, для прикладу, що ви написали спеціальний вид, що розширює UIView, в якому позиціонування підвидів є складним і не може бути виконано за допомогою автоматичної розстановки маски або iOS6. Налаштування позиціонування може бути здійснено шляхом переосмислення 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 не встановлений.


4
Або вам слід просто зателефонувати [self layoutIfNeeded]в блок анімації після встановлення крайових вставки.
Скотт Фістер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.