Подкласс UITableViewHeaderFooterView с автоматической компоновкой и перезагрузкой раздела не будет работать вместе - программирование

Подкласс UITableViewHeaderFooterView с автоматической компоновкой и перезагрузкой раздела не будет работать вместе

Я пытаюсь включить автомат в свой подкласс UITableViewHeaderFooterView. Класс довольно простой, всего две метки. Это полный подкласс:

@implementation MBTableDetailStyleFooterView

static void MBTableDetailStyleFooterViewCommonSetup(MBTableDetailStyleFooterView *_self) {
    UILabel *rightLabel = [[UILabel alloc] init];
    _self.rightLabel = rightLabel;
    rightLabel.translatesAutoresizingMaskIntoConstraints = NO;
    [_self.contentView addSubview:rightLabel];

    UILabel *leftLabel = [[UILabel alloc] init];
    _self.leftLabel = leftLabel;
    leftLabel.translatesAutoresizingMaskIntoConstraints = NO;
    [_self.contentView addSubview:leftLabel];

    NSDictionary *views = NSDictionaryOfVariableBindings(rightLabel, leftLabel);

    NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-10-[leftLabel]-(>=10)-[rightLabel]-10-|" options:0 metrics:nil views:views];
    [_self.contentView addConstraints:horizontalConstraints];

    // center views vertically in super view
    NSLayoutConstraint *leftCenterYConstraint = [NSLayoutConstraint constraintWithItem:leftLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_self.contentView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
    [_self.contentView addConstraint:leftCenterYConstraint];
    NSLayoutConstraint *rightCenterYConstraint = [NSLayoutConstraint constraintWithItem:rightLabel attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_self.contentView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0];
    [_self.contentView addConstraint:rightCenterYConstraint];

    // same height for both labels
    NSLayoutConstraint *sameHeightConstraint = [NSLayoutConstraint constraintWithItem:leftLabel attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:rightLabel attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
    [_self.contentView addConstraint:sameHeightConstraint];
}

+ (BOOL)requiresConstraintBasedLayout {
    return YES;
}

- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithReuseIdentifier:reuseIdentifier];
    MBTableDetailStyleFooterViewCommonSetup(self);
    return self;
}

@end

Этот класс используется как нижний колонтитул в первом разделе в таблицеView с двумя разделами. Первый раздел содержит динамические элементы. Второй раздел имеет только одну строку, которая используется для добавления новых элементов в первый раздел.

Если в первом разделе нет элементов, я скрываю footerView. Поэтому, когда я добавляю первый новый элемент, я должен перезагрузить раздел, чтобы появился footerView. Код, который делает все это, выглядит следующим образом:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
    if (indexPath.section == 1) {
        BOOL sectionNeedsReload = ([self.data count] == 0); // reload section when no data (and therefor no footer) was present before the add
        [self.data addObject:[NSDate date]];
        NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:[self.data count]-1 inSection:0];
        if (sectionNeedsReload) {
            [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationAutomatic];
        }
        else {
            [self.tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
        }
        [self configureFooter:(MBTableDetailStyleFooterView *)[tableView footerViewForSection:0] forSection:0];
    }
}

- (void)configureFooter:(MBTableDetailStyleFooterView *)footer forSection:(NSInteger)section {
    footer.leftLabel.text = @"Total";
    footer.rightLabel.text = [NSString stringWithFormat:@"%d", [self.data count]];
}

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section {
    MBTableDetailStyleFooterView *footer = nil;
    if (section == 0 && [self.data count]) {
        footer = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"Footer"];
        [self configureFooter:footer forSection:section];
    }
    return footer;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    CGFloat height = 0;
    if (section == 0 && [self.data count]) {
        height = 20.0f;
    }
    return height;
}

Ничего особенного. Тем не менее, , как только reloadSections:withRowAnimations: вызывается в моем tableView, он выдает исключение, потому что он "не может одновременно удовлетворять ограничениям".

Где-то в таблицеView добавлено ограничение на перенос маски для автоматического изменения размера для моего нижнего колонтитула.

(
    "<NSLayoutConstraint:0x718a1f0 H:[UILabel:0x7189130]-(10)-|   (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>",
    "<NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>",
    "<NSLayoutConstraint:0x718a0a0 H:|-(10)-[UILabel:0x71892c0]   (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>",
    "<NSAutoresizingMaskLayoutConstraint:0x7591ab0 h=--& v=--& H:[_UITableViewHeaderFooterContentView:0x7188df0(0)]>"
)

Когда я заменяю reloadSections:withRowAnimations: на вызов reloadData, добавляется ограничение авторезистивной маски, и все работает нормально.

Интересно, что исключение говорит мне, что он пытается сломать ограничение <NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>

Но когда я записываю ограничения в последующих вызовах на configureFooter:forSection:, это ограничение все еще существует, но ограничение маски для автоматического изменения размера отсутствует

Ограничения - это именно те, которые я установил.

(
    "<NSLayoutConstraint:0x718a0a0 H:|-(10)-[UILabel:0x71892c0]   (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>",
    "<NSLayoutConstraint:0x7189e30 H:[UILabel:0x71892c0]-(>=10)-[UILabel:0x7189130]>",
    "<NSLayoutConstraint:0x718a1f0 H:[UILabel:0x7189130]-(10)-|   (Names: '|':_UITableViewHeaderFooterContentView:0x7188df0 )>",
    "<NSLayoutConstraint:0x718a3f0 UILabel:0x71892c0.centerY == _UITableViewHeaderFooterContentView:0x7188df0.centerY>",
    "<NSLayoutConstraint:0x718a430 UILabel:0x7189130.centerY == _UITableViewHeaderFooterContentView:0x7188df0.centerY>",
    "<NSLayoutConstraint:0x718a4b0 UILabel:0x71892c0.height == UILabel:0x7189130.height>"
)

Откуда это ограничение автомасштабирования маски? Куда он идет?

Я что-то упустил? В первый раз, когда я посмотрел на автоматический макет, как неделю назад, так что это вполне возможно.

4b9b3361

Ответ 1

Рабочее решение с iOS 9

В подклассе UITableViewHeaderFooterView введите следующий код.

- (void)setFrame:(CGRect)frame {
    if (frame.size.width == 0) {
        return;
    }

    [super setFrame:frame];
}

Объяснение:

Tableview обрабатывает макет представлений заголовков, и он делает это, вручную манипулируя фреймами (да даже при включенном автозапуске).

Если вы проверяете ограничения ширины, которые находятся в представлениях заголовка/нижнего колонтитула, есть два, один из которых содержится в надзоре (представление таблицы) для ширины, а один из них содержится в самом представлении заголовка/нижнего колонтитула для ширины.

Ограничение, содержащееся в супер-представлении, представляет собой NSAutoresizingMaskLayoutConstraint, который является поддачей, что представление таблицы зависит от фреймов для управления заголовками. Переключение транслятовAutoresizingMaskIntoConstraints на NO в представлении заголовка аффективно нарушает его внешний вид, который еще один отдает.

Похоже, что в некоторых случаях эти представления заголовка/нижнего колонтитула будут иметь свои рамки с шириной в ноль, для меня это было, когда строки были вставлены, а виды заголовков были повторно использованы. Я предполагаю, что где-то в коде UITableView подготовка к анимации производится путем запуска кадра с нулевой шириной, даже если вы не используете анимацию.

Это решение должно работать хорошо и не должно влиять на производительность прокрутки.

Ответ 2

Я наткнулся на эту последнюю неделю.

То, как я устранил предупреждения, - изменить мои требуемые ограничения, чтобы иметь приоритет 999. Это работа, а не исправление, но она оборачивается исключениями, которые были выброшены, пойманы и зарегистрированы во время компоновки.

Вещи, которые не работали для меня.

Предложение установить estimatedRowHeight. Я попытался установить estimatedSectionHeaderHeight, но это не помогло. Установка estimatedSectionFooterHeight создала пустые нижние колонтитулы, где я не хотел их, что было немного странно.

Я также попытался установить translatesAutoresizingMaskIntoConstraints = NO; в представлении нижнего колонтитула заголовка и в его представлении содержимого. Ничто не избавилось от предупреждения, и один из них полностью нарушил макет.

Ответ 3

У меня была аналогичная проблема с только одной дополнительной меткой в ​​contentView. Попробуйте вставить

static void MBTableDetailStyleFooterViewCommonSetup(MBTableDetailStyleFooterView *_self) {
    _self.contentView.translatesAutoresizingMaskIntoConstraints = NO
    [...]
}

в первой строке в вашей функции MBTableDetailStyleFooterViewCommonSetup. Для меня это работает в сочетании с reloadSections:withRowAnimations:.

Update:

Я также добавил новое ограничение для contentView для использования всей ширины:

NSArray *horizontalConstraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:@{@"contentView" : _self.contentView}];
[_self.contentView.superview addConstraints:horizontalConstraints];

Ответ 4

Раздражающе, похоже, что UITableViewHeaderFooterView не любит постоянные ограничения, например.

| -10- [вид] -10- |

Я предполагаю, что при создании представление имеет нулевую ширину и не может удовлетворить ограничение, которое в этом случае должно было бы иметь ширину не менее 20 пикселей.

Для меня путь вокруг этого заключался в том, чтобы изменить мои постоянные ограничения на что-то вроде

| - (< = 10) - [вид] - (< = 10) - |

который должен удовлетворять нулевой ширине и должен давать желаемый запас при изменении размера.

Ответ 5

Я не вижу, как вы называете translatesAutoresizingMaskIntoConstraints = NO на нижнем колонтитуле. Должны ли вы делать это при его создании?

- (id)initWithReuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithReuseIdentifier:reuseIdentifier];
    self.translatesAutoresizingMaskIntoConstraints = NO;
    MBTableDetailStyleFooterViewCommonSetup(self);
    return self;
}

Ответ 6

У меня была аналогичная проблема, только один UILabel в contentView, и я использовал масонство:

_titleLabel = ({
    UILabel *label = [MLBUIFactory labelWithType:MALabelTypeB];
    [self.contentView addSubview:label];
    [label mas_makeConstraints:^(MASConstraintMaker */make) {
        make.centerY.equalTo(self.contentView);
        make.left.equalTo(self.contentView).offset(8);
        make.right.equalTo(self.contentView).offset(-8);
    }];

    label;
});

то я получил предупреждение:

(
"<MASLayoutConstraint:0x1742b2c60 UILabel:0x124557ee0.left == _UITableViewHeaderFooterContentView:0x12454c640.left + 8>",
"<MASLayoutConstraint:0x1742b3080 UILabel:0x124557ee0.right == _UITableViewHeaderFooterContentView:0x12454c640.right - 8>",
"<NSLayoutConstraint:0x174280780 _UITableViewHeaderFooterContentView:0x12454c640.width == 0>"
)
Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x1742b3080 UILabel:0x124557ee0.right == _UITableViewHeaderFooterContentView:0x12454c640.right - 8>

поэтому я попытался установить self.translatesAutoresizingMaskIntoConstraints = NO;, но для меня это не сработало, поэтому я сосредоточился на предупреждающем сообщении, и я смущен, почему левое ограничение работает, но правильно, когда я увидел _UITableViewHeaderFooterContentView:0x12454c640.width == 0, Я понял, может быть, почему правое ограничение является ограничивающим ограничением, тогда я изменил код:

_titleLabel = ({
    UILabel *label = [MLBUIFactory labelWithType:MALabelTypeB];
    [self.contentView addSubview:label];
    [label mas_makeConstraints:^(MASConstraintMaker */make) {
        make.centerY.equalTo(self.contentView);
        make.left.equalTo(self.contentView).offset(8);
        make.width.equalTo(@(SCREEN_WIDTH - 16));
    }];

    label;
});

Я заменил правое ограничение на ограничение ширины, и предупреждение исчезло.

Есть другой способ, чтобы предупреждение не было: self.contentView.translatesAutoresizingMaskIntoConstraints = NO;, НО рамка метки ошибочна.

Ответ 8

В iOS 11 я просто не смог получить UITableViewHeaderFooterView для правильной отображения.

Проблема заключалась в том, что я устанавливал свои ограничения, когда UITableViewHeaderFooterView считал, что он ограничивается, поэтому он всегда вызывает конфликты.

Здесь решение

override func layoutSubviews() {
    super.layoutSubviews()
    guard bounds != CGRect.zero else {
        return
    }
    makeConstraints()
}