Дизайн заголовка раздела UITableView в интерфейсе Builder

У меня есть файл xib с UITableView, для которого я хочу добавить собственное представление заголовка раздела, используя метод делегата tableView:viewForHeaderInSection:. Есть ли возможность спроектировать его в Interface Builder, а затем программным образом изменить некоторые его свойства представления?

My UITableView имеет больше заголовков разделов, поэтому создавая один UIView в Interface Builder, и возврат его не работает, потому что мне придется его дублировать, но нет хорошего способа его выполнения. Архивирование и разблокировка не работает для UIImage, поэтому UIImageView будет отображаться пустым.

Кроме того, я не хочу создавать их программно, потому что они слишком сложны, и полученный код будет трудно читать и поддерживать.

Изменить 1. Вот мой метод tableView:viewForHeaderInSection::

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    CGSize headerSize = CGSizeMake(self.view.frame.size.width, 100);

    /* wrapper */

    UIView *wrapperView = [UIView viewWithSize:headerSize];

    wrapperView.backgroundColor = [UIColor colorWithHexString:@"2670ce"];

    /* title */

    CGPoint titleMargin = CGPointMake(15, 8);

    UILabel *titleLabel = [UILabel labelWithText:self.categoriesNames[section] andFrame:CGEasyRectMake(titleMargin, CGSizeMake(headerSize.width - titleMargin.x * 2, 20))];

    titleLabel.textColor = [UIColor whiteColor];
    titleLabel.font = [UIFont fontWithStyle:FontStyleRegular andSize:14];

    [wrapperView addSubview:titleLabel];

    /* body wrapper */

    CGPoint bodyWrapperMargin = CGPointMake(10, 8);

    CGPoint bodyWrapperViewOrigin = CGPointMake(bodyWrapperMargin.x, CGRectGetMaxY(titleLabel.frame) + bodyWrapperMargin.y);
    CGSize bodyWrapperViewSize = CGSizeMake(headerSize.width - bodyWrapperMargin.x * 2, headerSize.height - bodyWrapperViewOrigin.y - bodyWrapperMargin.y);

    UIView *bodyWrapperView = [UIView viewWithFrame:CGEasyRectMake(bodyWrapperViewOrigin, bodyWrapperViewSize)];

    [wrapperView addSubview:bodyWrapperView];

    /* image */

    NSInteger imageSize = 56;
    NSString *imageName = [self getCategoryResourceItem:section + 1][@"image"];

    UIImageView *imageView = [UIImageView imageViewWithImage:[UIImage imageNamed:imageName] andFrame:CGEasyRectMake(CGPointZero, CGEqualSizeMake(imageSize))];

    imageView.layer.masksToBounds = YES;
    imageView.layer.cornerRadius = imageSize / 2;

    [bodyWrapperView addSubview:imageView];

    /* labels */

    NSInteger labelsWidth = 60;

    UILabel *firstLabel = [UILabel labelWithText:@"first" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 0, labelsWidth, 16)];

    [bodyWrapperView addSubview:firstLabel];

    UILabel *secondLabel = [UILabel labelWithText:@"second" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 20, labelsWidth, 16)];

    [bodyWrapperView addSubview:secondLabel];

    UILabel *thirdLabel = [UILabel labelWithText:@"third" andFrame:CGRectMake(imageSize + bodyWrapperMargin.x, 40, labelsWidth, 16)];

    [bodyWrapperView addSubview:thirdLabel];

    [@[ firstLabel, secondLabel, thirdLabel ] forEachView:^(UIView *view) {
        UILabel *label = (UILabel *)view;

        label.textColor = [UIColor whiteColor];
        label.font = [UIFont fontWithStyle:FontStyleLight andSize:11];
    }];

    /* line */

    UIView *lineView = [UIView viewWithFrame:CGRectMake(imageSize + labelsWidth + bodyWrapperMargin.x * 2, bodyWrapperMargin.y, 1, bodyWrapperView.frame.size.height - bodyWrapperMargin.y * 2)];

    lineView.backgroundColor = [UIColor whiteColorWithAlpha:0.2];

    [bodyWrapperView addSubview:lineView];

    /* progress */

    CGPoint progressSliderOrigin = CGPointMake(imageSize + labelsWidth + bodyWrapperMargin.x * 3 + 1, bodyWrapperView.frame.size.height / 2 - 15);
    CGSize progressSliderSize = CGSizeMake(bodyWrapperViewSize.width - bodyWrapperMargin.x - progressSliderOrigin.x, 30);

    UISlider *progressSlider = [UISlider viewWithFrame:CGEasyRectMake(progressSliderOrigin, progressSliderSize)];

    progressSlider.value = [self getCategoryProgress];

    [bodyWrapperView addSubview:progressSlider];

    return wrapperView;
}

и я хотел бы, чтобы он выглядел примерно так:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    SectionView *sectionView = ... // get the view that is already designed in the Interface Builder

    sectionView.headerText = self.categoriesNames[section];
    sectionView.headerImage = [self getCategoryResourceItem:section + 1][@"image"];

    sectionView.firstLabelText = @"first";
    sectionView.secondLabelText = @"second";
    sectionView.thirdLabelText = @"third";

    sectionView.progress = [self getCategoryProgress];

    return wrapperView;
}

Изменить 2. Я не использую файлы Storyboard, просто .xib. Кроме того, у меня нет UITableViewController, просто UIViewController, в который я добавил UITableView.

Ответ 1

Я, наконец, решил его использовать этот учебник, который в основном состоит из следующих (адаптированный к моему примеру):

  • Создайте класс SectionHeaderView, который подклассы UIView.
  • Создайте файл SectionHeaderView.xib и установите его File Owner CustomClass в класс SectionHeaderView.
  • Создайте свойство UIView в файле .m, например: @property (strong, nonatomic) IBOutlet UIView *viewContent;
  • Подключите .xib View к этой розетке viewContent.
  • Добавьте метод инициализации, который выглядит следующим образом:

    + (instancetype)header {
    
        SectionHeaderView *sectionHeaderView = [[SectionHeaderView alloc] init];
    
        if (sectionHeaderView) { // important part
            sectionHeaderView.viewContent = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:sectionHeaderView options:nil] firstObject];
    
            [sectionHeaderView addSubview:sectionHeaderView.viewContent];
    
            return sectionHeaderView;
        }
    
        return nil;
    }
    

Затем я добавил UILabel внутри файла .xib и подключил его к выходу labelCategoryName и реализовал метод setCategoryName: внутри класса SectionHeaderView следующим образом:

- (void)setCategoryName:(NSString *)categoryName {

    self.labelCategoryName.text = categoryName;
}

Затем я реализовал метод tableView:viewForHeaderInSection: следующим образом:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    SectionHeaderView *sectionHeaderView = [SectionHeaderView header];

    [sectionHeaderView setCategoryName:self.categoriesNames[section]];

    return sectionHeaderView;
}

И это наконец сработало. Каждая секция имеет собственное имя, а также UIImageView отображается правильно.

Надеюсь, что это помогает другим, которые снова и снова спотыкаются о одних и тех же неправильных решениях по всему миру, как и я.

Ответ 2

Раскадровка или XIB

  • Тот же Storyboard:

    return tableView.dequeueReusableCell(withIdentifier: "header")
    

    Два заголовка раздела

  • Отделить XIB (Дополнительный шаг: сначала необходимо зарегистрировать этот Nib):

    tableView.register(UINib(nibName: "XIBSectionHeader", bundle:nil),
                       forCellReuseIdentifier: "xibheader")
    

Для загрузки из Storyboard вместо XIB см. этот ответ.


Использование UITableViewCell для создания заголовка раздела в IB

Воспользуйтесь тем, что заголовок секции является регулярным UIView, а UITableViewCell тоже является UIView. В Конструктор интерфейсов перетащите ячейку просмотра таблицы из библиотеки объектов в свой содержимое прототипа таблицы.

Добавьте Идентификатор к недавно добавленной ячейке просмотра таблицы и настройте ее внешний вид в соответствии с вашими потребностями. В этом примере я использовал header.

Изменить ячейку

Используйте dequeueReusableCell:withIdentifier, чтобы найти заголовок раздела, так же, как и любую ячейку таблицы. Вам нужно будет поставить heightForHeaderInSection, который для ясности жестко запрограммирован как 44:

//MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView?
{
    // This is where you would change section header content
    return tableView.dequeueReusableCell(withIdentifier: "header")
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
    return 44
}

Swift 2 и ранее:

return tableView.dequeueReusableCellWithIdentifier("header") as? UIView
self.tableView.registerNib(UINib(nibName: "XIBSectionHeader", bundle:nil),
    forCellReuseIdentifier: "xibheader")

► Найдите это решение на GitHub и дополнительные сведения о Swift Рецепты.

Ответ 3

Решение Простой

Создайте один xib, создайте пользовательский интерфейс в соответствии с вашей документацией то в viewForHeaderInSection получить xib

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

        NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:@"HeaderView" owner:self options:nil];

       HeaderView *headerView = [nibArray objectAtIndex:0];

    return headerView;
} 

Ответ 4

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

Если это была моя проблема, вот как я ее разрешу.

ОРИГИНАЛЬНОЕ РЕШЕНИЕ

1)

В моем UIViewController, которому принадлежит представление таблицы, я также создаю представление, что шаблон для заголовка. Назначьте это IBOutlet. Это будет представление, которое вы можете редактировать с помощью Interface Builder.

2)

В вашем методе ViewDidLoad или (возможно, лучше) ViewWillAppear вы захотите сделать столько копий этого шаблона заголовка UIView, сколько вам нужно будет отображать для заголовков разделов.

Создание копий UIViews в памяти не является тривиальным, но это не сложно. Вот ответ из связанного вопроса, который показывает вам, как это сделать.

Добавьте копии в NSMutableArray (где индекс каждого объекта будет соответствовать разделам... вид в индексе 0 массива будет тем, что вы возвращаете для раздела 0, вид 1 в массиве для раздела 1, ec.).

3)

Вы не сможете использовать IBOutlets для элементов этого заголовка раздела (поскольку ваш код связывает только точки с одним конкретным видом из файла XIB).

Итак, для этого вы, вероятно, захотите использовать свойства тега представления для каждого из элементов пользовательского интерфейса в своем представлении заголовка, которые вы хотите изменить/изменить для каждого другого раздела. Вы можете установить эти теги через Interface Builder, а затем обращаться к ним программно в своем коде.

В вашем методе viewForHeaderInSection вы сделаете что-то вроде:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {

    if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
        return nil;
    }

    SectionView *sectionView = [self.arrayOfDuplicatedHeaderViews objectAtIndex: section];
    // my title view has a tag of 10
    UILabel *titleToModify = [sectionView viewWithTag: 10]; 
    if(titleToModify)
    {
        titleToModify.text = [NSString stringWithFormat:@"section %d", section];
    }

    return sectionView;
}

Имеет смысл?

РАЗНОЕ РЕШЕНИЕ

1)

Вам все равно нужен массив UIViews (или Section View "подклассов UIViews), но вы можете создать каждый из них с последующими вызовами для загрузки представления из собственного файла XIB.

Что-то вроде этого:

@implementation SectionView

+ (SectionView*) getSectionView
{
  NSArray* array = [[NSBundle mainBundle] loadNibNamed:@"SectionView" owner:nil options:nil];
  return [array objectAtIndex:0]; // assume that SectionView is the only object in the xib
}

@end

(более подробно найдено в ответе на этот связанный вопрос)

2)

Возможно, вы сможете использовать IBOutlets (но я не уверен на 100%), но свойства тегов снова могут работать очень хорошо.