如何使用Storyboard实现自定义表格视图的节头和节尾

180

不使用Storyboard,我们可以在画布上拖动一个UIView,进行布局,然后在tableView:viewForHeaderInSectiontableView:viewForFooterInSection委托方法中设置它。

如果使用Storyboard,我们该怎么做呢?因为我们无法在画布上拖动一个UIView。

16个回答

386

只需将原型单元格用作您的部分标题和/或页脚。

  • 添加一个额外的单元格,并在其中放置所需元素。
  • 将标识符设置为某个特定值(在我的情况下为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
虽然晚了一步,但要禁用章节标题视图上的点击,只需在viewForHeaderInSection方法中返回cell.contentView即可(无需添加任何自定义UIView)。 - paulvs
3
我会尝试在我的最新项目中实现这个功能,但如果你试图长按其中一个标题,它会崩溃。 - Hons
13
在iOS 6.0中,引入了dequeueReusableHeaderFooterViewWithIdentifier方法,现在比这个答案更受欢迎。但是,正确使用它现在需要更多步骤。我有一个指南@ http://samwize.com/2015/11/06/guide-to-customizing-uitableview-section-header-footer/。 - samwize
3
不要使用UITableViewCells来创建你的sectionHeaderViews,这会导致意外的行为。请改用UIView来代替。 - AppreciateIt
4
请不要将UITableViewCell用作表头视图。这样做会导致此类视觉故障非常难以调试 - 由于单元格的重用,表头有时会消失,您可能需要花费数小时才能找到问题所在,直到意识到UITableViewCell不适用于UITableView表头。 - raven_raven
显示剩余20条评论

99

我知道这个问题是关于iOS 5的,但为了日后读者的利益,请注意从iOS 6开始,我们现在可以使用dequeueReusableHeaderFooterViewWithIdentifier代替dequeueReusableCellWithIdentifier

所以在viewDidLoad中,调用registerNib:forHeaderFooterViewReuseIdentifier:registerClass:forHeaderFooterViewReuseIdentifier:。然后在viewForHeaderInSection中,调用tableView:dequeueReusableHeaderFooterViewWithIdentifier:。您不会使用此API的单元格原型(它是基于NIB的视图或通过编程创建的视图),但这是用于出列头部和尾部的新API。


13
叹气,可惜这两个有关使用 NIB 而非 Proto Cell 的清晰答案当前还未获得 5 票支持,而那个“用 Proto Cell 来 hack 它”的回答已经超过 200 票了。 - Benjohn
5
与使用单独的Xib文件不同,使用Tieme提供的技巧可以在同一故事板中设计你的界面。 - CedricSoubrie
你能否采用Storyboard替代独立的NIB文件呢? - Foriger
@Foriger - 目前还没有。在故事板表视图中,这是一个奇怪的遗漏,但它就是这样。如果你想要的话,可以使用那个笨拙的原型单元格hack,但我个人只是忍受NIBs的头/尾视图的烦恼。 - Rob
你是说接受的答案,因为它没有使用dequeueReusableHeaderFooterViewWithIdentifier,而可以使用它来编写代码,即使用特定于标题和页脚的单元格,而不是常规单元格? - mfaani
2
仅仅说它是“旧的”是不够强烈的,我个人认为。被接受的答案是一个绕过原型单元格无法用于标题和页脚视图的事实的笨拙方法。但我认为这是错误的,因为它假定头部和尾部的出列工作方式与单元格的出列方式相同(新版头部/尾部的API的存在表明可能并非如此)。被接受的答案是一个创造性的解决方法,在iOS 5时可以理解,但在iOS 6及更高版本中,最好使用专门设计用于标题/页脚重用的API。 - Rob

56

iOS 6.0及以上版本,使用新的dequeueReusableHeaderFooterViewWithIdentifier API时有所变化。

我写了一篇指南(在iOS 9上测试过),可以总结如下:

  1. 子类化UITableViewHeaderFooterView
  2. 创建包含头部/尾部中所有其他视图的容器视图的子类视图,并添加NIB
  3. viewDidLoad中注册NIB
  4. 实现viewForHeaderInSection并使用dequeueReusableHeaderFooterViewWithIdentifier获取头部/尾部

3
谢谢你的回答,这并不是一种“黑客”方式,而是正确的做法。同时,感谢你向我介绍UITableViewHeaderFooterVIew。顺便说一句,这个指南真的非常好! :) - Roboris
欢迎。苹果公司对于表格或集合视图的页眉/页脚实现文档化程度不高。就在昨天,我在通过storyboard设计时卡在了获取页眉的步骤上,具体可以参考这篇文章:http://samwize.com/2016/02/03/adding-header-footer-to-uicollectionview-using-storyboard/。 - samwize
2
这肯定是最高评分的答案! - Ben Williams
你能否解释一下如何通过Storyboard来实现,而不是使用xib?我这样做时,成功地出列了,但我的UILabel为空。 - bunkerdive

22

我使用Storyboard中的原型单元格在iOS7中使其工作。我的自定义节头视图中有一个按钮,触发了在Storyboard中设置的segue。

首先看一下Tieme的解决方案

正如pedro.m所指出的那样,问题在于点击分区标题会导致选择该分区中的第一个单元格。

正如Paul Von所指出的那样,通过返回单元格的contentView而不是整个单元格来解决这个问题。

但是,正如Hons所指出的那样,长按该节标题将导致应用崩溃。

解决方法是从contentView中删除任何gestureRecognizers。

-(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
没错,我刚刚测试了一下,它可以工作,所以我更新了我的帖子(http://hons82.blogspot.it/2014/05/uitableviewheader-done-right.html),其中包含我在研究过程中找到的所有可能的解决方案。非常感谢您提供的这个修复。 - Hons
好的答案...谢谢你 - Jogendra.Com

13
如果您使用故事板,可以在tableview中使用原型单元格来布局标头视图。设置唯一的ID和viewForHeaderInSection,您可以使用该ID取消排队单元格并将其转换为UIView。

11

如果您需要Swift实现,请按照被接受的答案中的说明进行操作,然后在您的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
}

2
由于某些原因,直到我覆盖了heightForHeaderInSection,它才对我起作用。 - Entalpi
这是相当老的代码。你应该发布另一个答案! - JZ.
我卡住了,因为我不知道我必须要重写heightForHeaderInSection。谢谢@Entalpi。 - guijob
1
@JZ。在你的Swift 3示例中,你使用了self.tableView.dequeueReusableHeaderFooterView进行deque,而在Swift 2中则使用了self.tableView.dequeueReusableCellWithIdentifier,它们之间有什么区别吗? - Vrutin Rathod
1
这真是救了我的命!将headerCell的高度添加上去,就可以让它可见了。 - CRey
@Vrutin 我已经撤销了pableiros在2016年11月的糟糕编辑 - Cœur

9

我想到的解决方案基本上与引入storyboard之前使用的解决方案相同。

创建一个新的、空的接口类文件。将UIView拖到画布上,按需要进行布局。

手动加载nib,在viewForHeaderInSection或viewForFooterInSection委托方法中分配给适当的标题/页脚部分。

我曾希望苹果公司通过storyboard简化这种情况,并继续寻找更好或更简单的解决方案。例如,自定义表头和表尾很容易添加。


苹果公司确实这样做了,如果您使用故事板单元格作为标题方法 :) https://dev59.com/1mox5IYBdhLWcg3wbDok - Tieme
2
除非是原型单元格,否则该方法仅适用于静态单元格。 - Jean-Denis Muys
1
一个非常简单的解决方案是使用Pixate;你可以进行很多自定义而不需要引用头文件。你只需要实现tableView:titleForHeaderInSection,这只需要一行代码。 - John Starr Dewar
5
那才是真正的解决方案...不幸的是它没有排在前面,所以我花了一些时间才找到它。我总结了我遇到的问题 http://hons82.blogspot.it/2014/05/uitableviewheader-done-right.html - Hons
很简单,只需拖放即可。 - Ricardo

5
当你返回单元格的contentView时,会遇到两个问题:
  1. 与手势相关的崩溃
  2. 不重用contentView (每次在viewForHeaderInSection调用时,都会创建新的单元格)
解决方案:

表头/表尾的包装类。 它只是一个容器,从UITableViewHeaderFooterView继承,并在其中保存单元格。

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

在您的UITableView中注册类(例如,在viewDidLoad中)

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

在您的UITableView委托中:

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

3
我曾遇到这样的情况:即使按照正确的步骤操作,Header也从未被重复使用。因此,我想提醒每个人一个小技巧:如果想让空的部分(0行)显示出来,请注意以下内容:
dequeueReusableHeaderFooterViewWithIdentifier不会重复使用Header,直到你至少返回一行数据。
希望这对你有所帮助。

2
我曾经采用以下方法来懒加载创建表头/表尾视图:
  • 在故事板中添加一个自由形式的视图控制器作为表头/表尾
  • 在视图控制器中处理表头所有内容
  • 在表视图控制器中提供一个可变的视图控制器数组,用[NSNull null]填充表头/表尾
  • 在viewForHeaderInSection / viewForFooterInSection中,如果视图控制器不存在,则使用storyboard的instantiateViewControllerWithIdentifier创建它,将其存储在数组中并返回视图控制器的视图

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接