it-swarm.com.ru

Разработка заголовка раздела UITableView в Интерфейсном Разработчике

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

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

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

Правка 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.

26
Iulian Onofrei

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

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

    + (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;
}

И это наконец-то сработало. Каждый раздел имеет собственное имя, а также UIImageViews отображаются правильно.

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

10
Iulian Onofrei

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

  1. То же Storyboard:

    return tableView.dequeueReusableCell(withIdentifier: "header")
    

     Two Section Headers

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

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

Чтобы загрузить из Storyboard вместо XIB, смотрите этот ответ переполнения стека .


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

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

Добавьте Identifier во вновь добавленную ячейка табличного представления и настройте его внешний вид в соответствии с вашими потребностями. Для этого примера я использовал header.

 Edit the cell

Используйте 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 Recipes .

76
SwiftArchitect

Решение - это простой способ 

Создайте один 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;
} 
1
Bevan

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

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

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

1)

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

2)

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

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

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

3)

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

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

В вашем методе 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 (или UIViews с подклассами "Section View"), но вы можете создать каждый из них с последовательными вызовами, чтобы загрузить представление из его собственного файла 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%), но свойства тегов еще раз могут работать довольно хорошо. 

0
Michael Dautermann