如何使用情节串连板实现自定义表视图部分的页眉和页脚

不使用情节串连板,我们可以简单地将一个 UIView拖到画布上,将其展开,然后在 tableView:viewForHeaderInSectiontableView:viewForFooterInSection委托方法中设置它。

我们如何使用 StoryBoard 来实现这一点,在 StoryBoard 中我们不能将 UIView 拖到画布上

152753 次浏览

我提出的解决方案基本上与引入故事板之前使用的解决方案相同。

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

手动加载笔尖,分配到 viewForHeaderInSection 或 viewForFooterInSection 委托方法中适当的 Header/footer 部分。

我曾希望苹果能用故事板简化这个场景,并继续寻找更好或更简单的解决方案。例如,可以直接添加自定义表头和表脚。

我过去常常懒惰地创建页眉/页脚视图:

  • 添加一个自由形式的视图控制器,用于节的页眉/页脚到故事板
  • 处理视图控制器中头部的所有内容
  • 在表视图控制器中,为用 [NSNull null]重新填充的部分头/尾提供了一个可变的视图控制器数组
  • 在 viewForHeaderInSection/viewForFooterInSection 中,如果视图控制器还不存在,使用情节串联板 instantiateViewControllerWithIdentifier 创建它,在数组中记住它,并返回视图控制器视图

如果你使用情节串连图板,你可以在表格视图中使用一个原型单元来布局你的头部视图。设置一个惟一的 ID 和 viewForHeaderInSection,您可以使用该 ID 取出单元格并将其强制转换为 UIView。

只需使用一个原型单元格作为部分的页眉和/或页脚。

  • 添加一个额外的单元格,并将您想要的元素放入其中。
  • 将标识符设置为特定的内容(在我的例子中是 SectionHeader)
  • 实现 tableView:viewForHeaderInSection:方法或 tableView:viewForFooterInSection:方法
  • 使用 [tableView dequeueReusableCellWithIdentifier:]获取标题
  • 实现 tableView:heightForHeaderInSection:方法。

(see screenhot)

-(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"];

我在 iOS7中使用情节串连图板中的一个原型单元使它工作。我有一个按钮在我的自定义部分标题视图,触发了一个接口,是在故事板设置。

时间的解决方案开始

正如 pedro.m 指出的那样,这样做的问题在于,点击节头会导致选择节中的第一个单元格。

正如 Paul Von 指出的,通过返回单元格的 contentView 而不是整个单元格就可以解决这个问题。

然而,正如 Hons 所指出的,长时间按下上述部分标题将导致应用程序崩溃。

解决方案是从 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; }

如果你不使用手势在你的部分标题视图,这个小黑客似乎可以做到这一点。

为了跟进 Damon 的建议,下面是我如何使标题可选择,就像一个普通的行与披露指示器。

我从 UIButton (子类名“ ButtonWithArgument”)中添加了一个 Button 子类,并删除了标题文本(粗体的“ Title”文本是原型单元格中的另一个 UILabel)

Button In Interface Builder

然后将 Button 设置为整个头视图,并使用 阿瓦里奥的把戏添加一个披露指示符

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

和论点一起按钮

#import <UIKit/UIKit.h>


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

ButtonWithArgument.m

#import "ButtonWithArgument.h"
@implementation ButtonWithArgument
@end

如果你需要一个快速实现的方法,请遵循已接受的答案上的指示,然后在你的 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
}

我知道这个问题是针对 iOS5的,但是为了未来读者的利益,请注意,有效的 iOS6我们现在可以使用 dequeueReusableHeaderFooterViewWithIdentifier而不是 dequeueReusableCellWithIdentifier

所以在 viewDidLoad中,调用 registerNib:forHeaderFooterViewReuseIdentifier:registerClass:forHeaderFooterViewReuseIdentifier:。然后在 viewForHeaderInSection中调用 tableView:dequeueReusableHeaderFooterViewWithIdentifier:。这个 API 不使用计算单元原型(它要么是基于 NIB 的视图,要么是编程创建的视图) ,但是这是用于出队页眉和页脚的新 API。

当你返回 cell 的 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;
}

在 iOS 6.0及以上版本中,新的 dequeueReusableHeaderFooterViewWithIdentifier API 改变了一些东西。

我写了一个 向导(在 iOS9上测试过) ,可以概括如下:

  1. UITableViewHeaderFooterView子类
  2. 使用子类视图创建 Nib,并添加1个容器视图,该视图包含页眉/页脚中的所有其他视图
  3. viewDidLoad中注册 Nib
  4. 实现 viewForHeaderInSection并使用 dequeueReusableHeaderFooterViewWithIdentifier返回页眉/页脚

您应该使用 时间的解决方案作为基础,但是忘记 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;
}

这里是 @ Vitaliy Gozhenko 的回答,在斯威夫特。
总结一下,您将创建一个包含 UITableViewCell 的 UITableViewHeaderFooterView。这个 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. Plug your tableview with this class in your viewDidLoad function:

    self.tableView.registerClass(CustomHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "SECTION_ID")
    
  3. When asking, for a section header, dequeue a CustomHeaderFooterView, and insert a cell into it

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

我曾经遇到过这样的问题: Header 即使执行了所有正确的步骤,也从来没有被重用过。

因此,作为一个提示说明,每个人谁想要实现的情况显示空部分(0行)是警告:

Dequeue eReusableHeaderFooterViewWithIdentifier 在返回至少一行之前不会重用标头

希望能有帮助

如果解决方案的标题是基于视图数组的,那该怎么办:

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])
}
}
  1. StoryBoard中添加单元格,并设置 reuseidentified

    sb

  2. 密码

    class TP_TaskViewTableViewSectionHeader: UITableViewCell{
    }
    

    还有

    link

  3. 用途:

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let header = tableView.dequeueReusableCell(withIdentifier: "header", for: IndexPath.init(row: 0, section: section))
    return header
    }
    

与 laszlo answer 类似,但是您可以为表单元格和节标题单元格重用相同的原型单元格。将下面的前两个函数添加到 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
}