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

176

Без использования раскадровки мы могли бы просто перетащить объект UIViewна холст, выложить его, а затем установить его в методах tableView:viewForHeaderInSectionили tableView:viewForFooterInSectionделегата.

Как мы можем сделать это с помощью StoryBoard, где мы не можем перетащить UIView на холст

Симус
источник

Ответы:

91

Я знаю, что этот вопрос был для iOS 5, но для будущих читателей, обратите внимание, что эффективную iOS 6 мы теперь можем использовать dequeueReusableHeaderFooterViewWithIdentifierвместо dequeueReusableCellWithIdentifier.

Так viewDidLoad, вызов либо registerNib:forHeaderFooterViewReuseIdentifier:или registerClass:forHeaderFooterViewReuseIdentifier:. Тогда viewForHeaderInSectionпозвони tableView:dequeueReusableHeaderFooterViewWithIdentifier:. Вы не используете прототип ячейки с этим API (это либо представление на основе NIB, либо представление, созданное программным способом), но это новый API для устаревших верхних и нижних колонтитулов.

обкрадывать
источник
13
Вздох Позор, что эти два чистых ответа об использовании NIB вместо протоклетки в настоящее время имеют менее 5 голосов, а ответ «взломать его с помощью
протоклеток»
5
Разница в том, что с помощью взлома от Tieme вы можете создать свой дизайн в одной раскадровке и не использовать отдельный Xib
CedricSoubrie
Можете ли вы как-то избежать использования отдельного файла NIB и вместо этого использовать раскадровку?
Форигер
@Foriger - Не сейчас. Это странное упущение в представлениях таблицы раскадровки, но это то, что есть. Если хотите, используйте этот хакерский хак с прототипами ячеек, но лично я просто смирился с раздражением NIB для представлений верхнего / нижнего колонтитула.
Роб
2
Просто сказать, что он «старый» недостаточно силен, ИМХО. Принятый ответ - хитрый способ обойти тот факт, что у вас не может быть прототипа ячеек для представлений верхнего и нижнего колонтитула. Но я думаю, что это просто неправильно, потому что предполагает, что удаление заголовков и нижних колонтитулов работает так же, как и удаление ячеек (что, как предполагает наличие более нового API для верхних и нижних колонтитулов, может не иметь места). Принятый ответ - творческий обходной путь, который был понятен в iOS 5, но в iOS 6 и более поздних версиях лучше использовать API, явно разработанный для повторного использования верхнего / нижнего колонтитула.
Роб
384

Просто используйте ячейку прототипа в качестве заголовка раздела и / или нижнего колонтитула.

  • добавьте дополнительную ячейку и поместите в нее нужные элементы.
  • установить идентификатор для чего-то конкретного (в моем случае SectionHeader)
  • реализовать tableView:viewForHeaderInSection:метод или tableView:viewForFooterInSection:метод
  • использовать, [tableView dequeueReusableCellWithIdentifier:]чтобы получить заголовок
  • реализовать tableView:heightForHeaderInSection:метод.

(см. скриншот)

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    static NSString *CellIdentifier = @"SectionHeader"; 
    UITableViewCell *headerView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (headerView == nil){
        [NSException raise:@"headerView == nil.." format:@"No cells with matching CellIdentifier loaded from your storyboard"];
    }
    return headerView;
}  

Редактировать: Как изменить заголовок заголовка (прокомментированный вопрос):

  1. Добавить метку в ячейку заголовка
  2. установить метку метки на определенный номер (например, 123)
  3. В вашем tableView:viewForHeaderInSection:методе получите метку по телефону:
    UILabel *label = (UILabel *)[headerView viewWithTag:123]; 
  1. Теперь вы можете использовать метку, чтобы установить новый заголовок:
    [label setText:@"New Title"];
Свяжи меня
источник
7
Поздно, но чтобы отключить щелчки в представлении заголовка раздела, просто верните cell.contentView в viewForHeaderInSection методе (не нужно добавлять какие-либо пользовательские UIViews).
paulvs
3
@PaulVon Я пытался сделать это в моем последнем проекте, но если вы сделаете это, просто попробуйте надавить на один из ваших заголовков, и он рухнет
Хонс
13
В iOS 6.0, dequeueReusableHeaderFooterViewWithIdentifierпредставлен, и теперь предпочтительнее, чем этот ответ. Но для правильного использования теперь требуется больше шагов. У меня есть руководство @ samwize.com/2015/11/06/... .
samwize
3
Не создавайте ваши sectionHeaderViews с помощью UITableViewCells, это приведет к неожиданному поведению. Вместо этого используйте UIView.
Ценю
4
Пожалуйста, никогда не используйте UITableViewCellв качестве заголовка. Вам будет очень трудно отлаживать визуальные глюки - заголовок иногда исчезает из-за того, как ячейки удаляются, и вы будете часами искать почему, пока не поймете, что UITableViewCellон не принадлежит UITableViewзаголовку.
raven_raven
53

В iOS 6.0 и выше все изменилось с новым dequeueReusableHeaderFooterViewWithIdentifierAPI.

Я написал руководство (протестировано на iOS 9), которое можно резюмировать так:

  1. Подкласс UITableViewHeaderFooterView
  2. Создайте Nib с представлением подкласса и добавьте 1 представление контейнера, которое содержит все другие представления в верхнем / нижнем колонтитуле
  3. Зарегистрировать перо в viewDidLoad
  4. Реализуйте viewForHeaderInSectionи используйте, dequeueReusableHeaderFooterViewWithIdentifierчтобы вернуть верхний / нижний колонтитул
samwize
источник
2
Спасибо за этот ответ, который НЕ является взломом, и правильный способ сделать вещи. Также спасибо за то, что познакомили меня с UITableViewHeaderFooterVIew. Кстати, руководство просто отлично! :)
Роборис
Добро пожаловать. Реализация верхнего / нижнего колонтитула для представления таблицы или коллекции не очень хорошо документирована Apple. Буквально вчера я застрял в заголовке геттера для показа при проектировании через storyboad .
samwize
1
Это должен быть лучший ответ наверняка!
Бен Уильямс
Не могли бы вы объяснить, как это сделать с помощью раскадровки, а не XIB? При этом я успешно удаляю из очереди, но мои UILabels равны нулю.
бункерное погружение
22

Я получил его в iOS7, используя прототип ячейки в раскадровке. У меня в пользовательском представлении заголовка раздела есть кнопка, которая запускает переход, настроенный в раскадровке.

Начните с решения Tieme

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

Как указывает Пол Фон, это исправляется возвращением contentView ячейки вместо всей ячейки.

Однако, как отмечает Хонс, долгое нажатие на заголовок раздела приведет к сбою приложения.

Решение состоит в том, чтобы удалить любые жесты-распознаватели из contentView.

-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
     static NSString *CellIdentifier = @"SectionHeader";
     UITableViewCell *sectionHeaderView = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

     while (sectionHeaderView.contentView.gestureRecognizers.count) {
         [sectionHeaderView.contentView removeGestureRecognizer:[sectionHeaderView.contentView.gestureRecognizers objectAtIndex:0]];
     }

     return sectionHeaderView.contentView; }

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

Дэймон
источник
2
Ты прав. Я только что проверил это и оно работает, поэтому я обновил свой пост ( hons82.blogspot.it/2014/05/uitableviewheader-done-right.html ), содержащий все возможные решения, которые я нашел в ходе своего исследования. Огромное спасибо за это исправление
Hons
Хороший ответ ... Спасибо, парень
Jogendra.Com
13

Если вы используете раскадровки, вы можете использовать ячейку прототипа в табличном представлении для компоновки представления заголовка. Установите уникальный идентификатор и viewForHeaderInSection, вы можете удалить ячейку с этим идентификатором и привести ее к UIView.

barksten
источник
12

Если вам нужна быстрая реализация этого, следуйте инструкциям на принятом ответе, а затем в вашем UITableViewController реализуйте следующие методы:

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    return tableView.dequeueReusableCell(withIdentifier: "CustomHeader")
}

override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
    return 75
}
JZ.
источник
2
По некоторым причинам это не работало для меня, пока я не переопределил heightForHeaderInSection также.
Энтальпи
Это довольно старый код. Вы должны опубликовать еще один ответ!
JZ.
Я застрял, потому что я не знал, что мне придется переопределить heightForHeaderInSection. Спасибо @Entalpi
guijob
1
@JZ. В вашем примере Swift 3 вы удаляете со следующими self.tableView.dequeueReusableHeaderFooterView и Swift 2: self.tableView.dequeueReusableCellWithIdentifier есть ли разница?
Врутин Ратод
1
Это спасло мою задницу! добавление высоты к headerCell делает его видимым.
CRey
9

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

Создайте новый, пустой файл класса интерфейса. Перетащите UIView на холст, разметьте по желанию.

Загрузите перо вручную, назначьте соответствующий раздел верхнего / нижнего колонтитула в методах делегирования viewForHeaderInSection или viewForFooterInSection.

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

Симус
источник
Хорошо, что Apple сделала, если вы используете ячейку раскадровки в качестве метода заголовка :) stackoverflow.com/questions/9219234/#11396643
Tieme
2
За исключением того, что работает только для ячеек-прототипов, а не статических ячеек.
Жан-Дени Мюис
1
Одним из действительно простых решений является использование Pixate ; Вы можете сделать довольно много настроек без получения ссылки на заголовок. Все, что вам нужно реализовать tableView:titleForHeaderInSection, это однострочник.
Джон Старр Дьюар
5
Это реальное решение ... к сожалению, оно не на высоте, поэтому мне понадобилось время, чтобы выяснить это. Я суммировал проблемы, которые у меня были hons82.blogspot.it/2014/05/uitableviewheader-done-right.html
Hons
Это проще, просто перетащите.
Рикардо
5

Когда вы возвращаете contentView ячейки, у вас будет 2 проблемы:

  1. авария, связанная с жестами
  2. вы не используете повторно contentView (каждый раз при viewForHeaderInSectionвызове вы создаете новую ячейку)

Решение:

Класс Wrapper для верхнего / нижнего колонтитула таблицы. Это просто контейнер, унаследованный от UITableViewHeaderFooterView, который содержит ячейку внутри

https://github.com/Magnat12/MGTableViewHeaderWrapperView.git

Зарегистрируйте класс в вашем UITableView (например, в viewDidLoad)

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tableView registerClass:[MGTableViewHeaderWrapperView class] forHeaderFooterViewReuseIdentifier:@"ProfileEditSectionHeader"];
}

В вашем UITableViewDelegate:

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    MGTableViewHeaderWrapperView *view = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"ProfileEditSectionHeader"];

    // init your custom cell
    ProfileEditSectionTitleTableCell *cell = (ProfileEditSectionTitleTableCell * ) view.cell;
    if (!cell) {
        cell = [tableView dequeueReusableCellWithIdentifier:@"ProfileEditSectionTitleTableCell"];
        view.cell = cell;
    }

    // Do something with your cell

    return view;
}
Виталий Гоженко
источник
2

Раньше я делал лениво для создания представлений верхнего / нижнего колонтитула:

  • Добавьте контроллер представления произвольной формы для верхнего / нижнего колонтитула раздела на раскадровку
  • Обработайте все вещи для заголовка в контроллере представления
  • В контроллере табличного представления предоставляют изменяемый массив контроллеров представления для верхних / нижних колонтитулов раздела, заполненных [NSNull null]
  • В viewForHeaderInSection / viewForFooterInSection, если контроллер представления еще не существует, создайте его с раскадровками instantiateViewControllerWithIdentifier, запомните его в массиве и верните представление контроллеров представления
Гадюка берусь
источник
2

У меня были проблемы в сценарии, когда заголовок никогда не использовался, даже когда я делал все необходимые шаги.

Так что в качестве подсказки всем, кто хочет достичь ситуации с показом пустых секций (0 строк), следует предупредить, что:

dequeueReusableHeaderFooterViewWithIdentifier не будет повторно использовать заголовок, пока вы не вернете хотя бы одну строку

Надеюсь, поможет

Хуан Педро Лосано
источник
1

Чтобы прокомментировать предложение Дэймона , вот как я сделал заголовок выбираемым, как обычная строка с индикатором раскрытия.

Я добавил Button подклассы из UIButton (имя подкласса "ButtonWithArgument") в ячейку прототипа заголовка и удалил текст заголовка (жирный текст "Title" - это еще одна UILabel в ячейке прототипа)

Кнопка в Интерфейсном Разработчике

затем установите кнопку на весь заголовок и добавьте индикатор раскрытия с помощью хитрости Avario

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    static NSString *CellIdentifier = @"PersonGroupHeader";
    UITableViewCell *headerView = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if(headerView == nil)
    {
        [NSException raise:@"headerView == nil, PersonGroupTableViewController" format:[NSString stringWithFormat:@"Storyboard does not have prototype cell with identifier %@",CellIdentifier]];
    }

    //  https://stackoverflow.com/a/24044628/3075839
    while(headerView.contentView.gestureRecognizers.count)
    {
        [headerView.contentView removeGestureRecognizer:[headerView.contentView.gestureRecognizers objectAtIndex:0]];
    }


    ButtonWithArgument *button = (ButtonWithArgument *)[headerView viewWithTag:4];
    button.frame = headerView.bounds; // set tap area to entire header view
    button.argument = [[NSNumber alloc] initWithInteger:section]; // from ButtonWithArguments subclass
    [button addTarget:self action:@selector(headerViewTap:) forControlEvents:UIControlEventTouchUpInside];

    // https://stackoverflow.com/a/20821178/3075839
    UITableViewCell *disclosure = [[UITableViewCell alloc] init];
    disclosure.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    disclosure.userInteractionEnabled = NO;
    disclosure.frame = CGRectMake(button.bounds.origin.x + button.bounds.size.width - 20 - 5, // disclosure 20 px wide, right margin 5 px
          (button.bounds.size.height - 20) / 2,
          20,
          20);
    [button addSubview:disclosure];

    // configure header title text

    return headerView.contentView;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 35.0f;
}

-(void) headerViewTap:(UIGestureRecognizer *)gestureRecognizer;
{
    NSLog(@"header tap");
    NSInteger section = ((NSNumber *)sender.argument).integerValue;
    // do something here
}

ButtonWithArgument.h

#import <UIKit/UIKit.h>

@interface ButtonWithArgument : UIButton
@property (nonatomic, strong) NSObject *argument;
@end

ButtonWithArgument.m

#import "ButtonWithArgument.h"
@implementation ButtonWithArgument
@end
MarkF
источник
1

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

Поэтому после того, как вы добавили в свой пользовательский вид заголовка ячейки все причудливые AutoLayoutвещи, просто удалите его из очереди и верните contentView после настройки, например:

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
 static NSString *CellIdentifier = @"SectionHeader"; 

    SettingsTableViewCell *sectionHeaderCell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    sectionHeaderCell.myPrettyLabel.text = @"Greetings";
    sectionHeaderCell.contentView.backgroundColor = [UIColor whiteColor]; // don't leave this transparent

    return sectionHeaderCell.contentView;
}  
Laszlo
источник
1

Как насчет решения, в котором заголовок основан на массиве представления:

class myViewController: UIViewController {
    var header: [UILabel] = myStringArray.map { (thisTitle: String) -> UILabel in
        let headerView = UILabel()
            headerView.text = thisTitle
    return(headerView)
}

Далее в делегате:

extension myViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return(header[section])
    }
}
CyrIng
источник
1
  1. Добавить ячейку StoryBoardи установитьreuseidentified

    С.Б.

  2. Код

    class TP_TaskViewTableViewSectionHeader: UITableViewCell{
    }
    

    и

    ссылка на сайт

  3. Использование:

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = tableView.dequeueReusableCell(withIdentifier: "header", for: IndexPath.init(row: 0, section: section))
        return header
    }
    
leo.tan
источник
2
Это неправильный способ добавить заголовок
Маниш Мальвия,
2
тогда какой путь?
Прамод Шукла
1

Аналогично ответу laszlo, но вы можете повторно использовать одну и ту же ячейку прототипа как для ячейки таблицы, так и для ячейки заголовка раздела. Добавьте первые две функции ниже в ваш подкласс UIViewController

override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell") as! DataCell
    cell.data1Label.text = "DATA KEY"
    cell.data2Label.text = "DATA VALUE"
    return cell
}

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

// Example of regular data cell dataDelegate to round out the example
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "DataCell", for: indexPath) as! PlayerCell

    cell.data1Label.text = "\(dataList[indexPath.row].key)"
    cell.data2Label.text = "\(dataList[indexPath.row].value)"
    return cell
}
Ричард Лего
источник
0

Вот ответ @Vitaliy Gozhenko , в Swift.
Подводя итог, вы создадите UITableViewHeaderFooterView, который содержит UITableViewCell. Этот UITableViewCell будет «извлекаемым», и вы можете создать его в своей раскадровке.

  1. Создайте класс UITableViewHeaderFooterView

    class CustomHeaderFooterView: UITableViewHeaderFooterView {
    var cell : UITableViewCell? {
        willSet {
            cell?.removeFromSuperview()
        }
        didSet {
            if let cell = cell {
                cell.frame = self.bounds
                cell.autoresizingMask = [UIViewAutoresizing.FlexibleHeight, UIViewAutoresizing.FlexibleWidth]
                self.contentView.backgroundColor = UIColor .clearColor()
                self.contentView .addSubview(cell)
            }
        }
    }
    
  2. Подключите табличное представление к этому классу в функции viewDidLoad:

    self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. При запросе заголовка раздела удалите из очереди CustomHeaderFooterView и вставьте в него ячейку.

    func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let view = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("SECTION_ID") as! CustomHeaderFooterView
        if view.cell == nil {
            let cell = self.tableView.dequeueReusableCellWithIdentifier("Cell")
            view.cell = cell;
        }
    
        // Fill the cell with data here
    
        return view;
    }
    
CedricSoubrie
источник