web-dev-qa-db-ja.com

ストーリーボードでカスタムテーブルビューセクションのヘッダーとフッターを実装する方法

ストーリーボードを使用せずに、単純にUIViewをキャンバスにドラッグし、レイアウトしてからtableView:viewForHeaderInSectionまたはtableView:viewForFooterInSectionデリゲートメソッドに設定できます。

IViewをキャンバスにドラッグできないStoryBoardでこれをどのように実現しますか

172
Seamus

この質問はiOS 5に関するものでしたが、将来の読者のために、効果的なiOS 6ではdequeueReusableHeaderFooterViewWithIdentifierの代わりにdequeueReusableCellWithIdentifierを使用できることに注意してください。

したがって、viewDidLoadで、registerNib:forHeaderFooterViewReuseIdentifier:またはregisterClass:forHeaderFooterViewReuseIdentifier:を呼び出します。次に、viewForHeaderInSectionで、tableView:dequeueReusableHeaderFooterViewWithIdentifier:を呼び出します。このAPI(NIBベースのビューまたはプログラムで作成されたビューのいずれか)でセルプロトタイプを使用しませんが、これはデキューされたヘッダーおよびフッター用の新しいAPIです。

88
Rob

プロトタイプセルをセクションヘッダーおよび/またはフッターとして使用します。

  • 余分なセルを追加して、そこに目的の要素を配置します。
  • 識別子を特定のものに設定します(私の場合は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"];
380
Tieme

IOS 6.0以降では、新しいdequeueReusableHeaderFooterViewWithIdentifier APIによって状況が変わりました。

ガイド (iOS 9でテスト済み)を作成しました。これは次のように要約できます。

  1. サブクラスUITableViewHeaderFooterView
  2. サブクラスビューでペン先を作成し、ヘッダー/フッターに他のすべてのビューを含む1つのコンテナービューを追加します
  3. ペン先をviewDidLoadに登録します
  4. viewForHeaderInSectionを実装し、dequeueReusableHeaderFooterViewWithIdentifierを使用してヘッダー/フッターを取得します
52
samwize

ストーリーボードのプロトタイプセルを使用して、iOS7で動作するようにしました。カスタムセクションのヘッダービューに、ストーリーボードに設定されているセグエをトリガーするボタンがあります。

Tiemeのソリューション で開始

Pedro.mが指摘しているように、これに関する問題は、セクションヘッダーをタップすると、セクションの最初のセルが選択されることです。

Paul Vonが指摘しているように、これはセル全体ではなくセルのcontentViewを返すことで修正されます。

ただし、Honsが指摘しているように、セクションヘッダーを長押しするとアプリがクラッシュします。

解決策は、contentViewからジェスチャーRecognizersを削除することです。

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

セクションヘッダービューでジェスチャを使用していない場合は、この小さなハックで完了したようです。

22
damon

ストーリーボードを使用する場合、TableViewのプロトタイプセルを使用してヘッダービューをレイアウトできます。一意のidとviewForHeaderInSectionを設定すると、そのIDを持つセルをデキューしてUIViewにキャストできます。

13
barksten

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
}
11
JZ.

私が思いついた解決策は、基本的に、ストーリーボードの導入前に使用したものと同じ解決策です。

新しい空のインターフェイスクラスファイルを作成します。 UIViewをキャンバスにドラッグし、必要に応じてレイアウトします。

Nibを手動でロードし、viewForHeaderInSectionまたはviewForFooterInSectionデリゲートメソッドで適切なヘッダー/フッターセクションに割り当てます。

Appleがストーリーボードでこのシナリオを単純化し、より良いまたはよりシンプルなソリューションを探し続けることを望んでいました。たとえば、カスタムテーブルのヘッダーとフッターは簡単に追加できます。

9
Seamus

セルのcontentViewを返すと、2つの問題が発生します。

  1. ジェスチャーに関連するクラッシュ
  2. contentViewを再利用しない(viewForHeaderInSection呼び出しのたびに、新しいセルを作成する)

溶液:

テーブルheader\footerのラッパークラス。 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;
}
5

ヘッダー/フッタービューを遅延的に作成するために、次のことを実行していました。

  • ストーリーボードにセクションヘッダー/フッターのフリーフォームビューコントローラーを追加する
  • View Controllerのヘッダーのすべてを処理します
  • Table View Controllerでは、[NSNull null]が再入力されたセクションヘッダー/フッターにView Controllerの可変配列を提供します
  • ViewControllerがまだ存在しない場合、viewForHeaderInSection/viewForFooterInSectionで、ストーリーボードinstantiateViewControllerWithIdentifierを使用して作成し、配列に記憶してView Controllerビューを返します
2
Vipera Berus

ヘッダーが適切な手順をすべて実行しても再利用されないというシナリオで、私は問題を抱えていました。

したがって、空のセクション(0行)を表示する状況を達成したいすべての人へのヒントとして、次の点に注意してください。

dequeueReusableHeaderFooterViewWithIdentifierは、少なくとも1行を返すまでヘッダーを再利用しません

それが役に立てば幸い

2

Damonの提案 をフォローアップするために、開示インジケータのある通常の行のようにヘッダーを選択可能にした方法を次に示します。

UIButtonからサブクラス化されたボタン(サブクラス名「ButtonWithArgument」)をヘッダーのプロトタイプセルに追加し、タイトルテキストを削除しました(太字の「タイトル」テキストはプロトタイプセル内の別のUILabelです)

Button In Interface Builder

次に、ボタンをヘッダービュー全体に設定し、 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
1
MarkF

ヘッダーがビュー配列に基づいているソリューションについてはどうですか:

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
CyrIng

Tieme's solution をベースとして使用する必要がありますが、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;
}  
1
Laszlo
  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
    }
    
0
leo.tan

これは @ Vitaliy Gozhenkoの答え 、Swiftにあります。
要約すると、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. 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;
    }
    
0
CedricSoubrie

Laszloの回答に似ていますが、テーブルセルとセクションヘッダーセルの両方に同じプロトタイプセルを再利用できます。以下の最初の2つの関数を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
Richard Legault