ストーリーボードで、複数のコントローラーで使用するカスタムセルを作成するにはどうすればよいですか?


216

作業中のアプリでストーリーボードを使用しようとしています。アプリにはリストユーザーがあり、それぞれに他のコレクションが含まれています(リストのメンバー、ユーザーが所有するリスト)。だから、それに応じてListCellUserCellクラスがあります。目標は、それらをアプリ全体で再利用できるようにすることです(つまり、私の任意のtableviewコントローラーで)。

ここで問題が発生します。

どのストーリーコントローラーでも再利用できるカスタムのテーブルビューセルをストーリーボードに作成するにはどうすればよいですか?

これまでに私が試した具体的なことを次に示します。

  • コントローラー#1で、プロトタイプセルを追加し、クラスをUITableViewCellサブクラスに設定し、再利用IDを設定し、ラベルを追加して、それらをクラスのアウトレットに配線しました。コントローラー#2で、空のプロトタイプセルを追加し、同じクラスに設定して、以前と同じようにIDを再利用します。実行すると、コントローラー#2にセルが表示されるときにラベルが表示されません。コントローラー#1で正常に動作します。

  • 各セルタイプを異なるNIBで設計し、適切なセルクラスに配線しました。ストーリーボードで、空のプロトタイプセルを追加し、そのクラスを設定して、自分のセルクラスを参照するためにIDを再利用しました。コントローラのviewDidLoadメソッドで、それらのNIBファイルを再利用IDに登録しました。表示すると、両方のコントローラーのセルはプロトタイプのように空でした。

  • 両方のコントローラーのプロトタイプを空にしてクラスを設定し、IDを私のセルクラスに再利用します。セルのUIを完全にコードで構築しました。セルはすべてのコントローラーで完全に機能します。

2番目のケースでは、プロトタイプが常にNIBをオーバーライドしていると思います。プロトタイプセルを強制終了した場合、NIBを再利用IDに登録すると機能します。しかし、セルから他のフレームにセグエをセットアップすることはできません。これが、ストーリーボードを使用することの本質です。

結局のところ、ストーリーボードでテーブルビューベースのフローを配線し、コードではなく視覚的にセルレイアウトを定義するという2つのことが必要です。今のところ、両方を取得する方法はわかりません。

回答:


205

私が理解しているように、あなたは次のことを望んでいます:

  1. 複数のストーリーボードシーンで使用できるセルをIBで設計します。
  2. セルがあるシーンに応じて、そのセルから一意のストーリーボードセグエを構成します。

残念ながら、現在これを行う方法はありません。以前の試行がうまくいかなかった理由を理解するには、ストーリーボードとプロトタイプのテーブルビューセルがどのように機能するかについてさらに理解する必要があります。(これらの他の試みがうまくいかなかった理由が気にならない場合は、ここから離れてください。バグを報告することを提案する以外に、魔法のような回避策はありません。)

ストーリーボードは、本質的に、.xibファイルのコレクションにすぎません。ストーリーボードからプロトタイプセルがいくつかあるテーブルビューコントローラをロードすると、次のようになります。

  • 各プロトタイプセルは、実際には独自の埋め込まれたミニnibです。そのため、テーブルビューコントローラーがロードされると、プロトタイプセルのnibとcallがそれぞれ実行されます-[UITableView registerNib:forCellReuseIdentifier:]
  • テーブルビューは、コントローラーにセルを要求します。
  • あなたはおそらく電話する -[UITableView dequeueReusableCellWithIdentifier:]
  • 特定の再利用識別子を持つセルを要求すると、nibが登録されているかどうかがチェックされます。含まれている場合は、そのセルのインスタンスをインスタンス化します。これは次のステップで構成されています。

    1. セルのペン先で定義されているように、セルのクラスを見てください。を呼び出し[[CellClass alloc] initWithCoder:]ます。
    2. この-initWithCoder:メソッドは、サブビューを確認して追加し、ペン先で定義されたプロパティを設定します。(IBOutletsはおそらくここでも接続されていますが、テストはしていませんが、で発生する可能性があります-awakeFromNib
  • セルは自由に構成できます。

ここで注意すべき重要な点は、セルのクラスとセルの視覚的外観との間に違いがあることです。同じクラスの2つの別々のプロトタイプセルを作成できますが、サブビューのレイアウトはまったく異なります。実際、デフォルトのUITableViewCellスタイルを使用している場合、これがまさに何が起こっているかです。たとえば、「デフォルト」スタイルと「サブタイトル」スタイルはどちらも同じUITableViewCellクラスで表されます。

これは重要です。セルのクラスには、特定のビュー階層との1対1の相関関係はありません。ビュー階層は、この特定のコントローラーに登録されたプロトタイプセルの内容によって完全に決定されます。

また、セルの再利用識別子が一部のグローバルセルディスペンサリーに登録されていないことにも注意してください。再利用識別子は、単一のUITableViewインスタンスのコンテキスト内でのみ使用されます。


この情報を踏まえて、上記の試みで何が起こったかを見てみましょう。

コントローラー#1でプロトタイプセルを追加し、クラスをUITableViewCellサブクラスに設定し、再利用IDを設定し、ラベルを追加して、それらをクラスのアウトレットに配線しました。コントローラー#2で、空のプロトタイプセルを追加し、同じクラスに設定して、以前と同じようにIDを再利用しました。実行すると、コントローラー#2にセルが表示されるときにラベルが表示されません。コントローラー#1で正常に動作します。

これは予想通りです。両方のセルに同じクラスがありましたが、コントローラー#2のセルに渡されたビュー階層にはサブビューがまったくありませんでした。つまり、プロトタイプに入力したとおりの空のセルができました。

各セルタイプを異なるNIBで設計し、適切なセルクラスに配線しました。ストーリーボードで、空のプロトタイプセルを追加し、そのクラスを設定して、自分のセルクラスを参照するようにIDを再利用しました。コントローラーのviewDidLoadメソッドで、再利用IDのNIBファイルを登録しました。表示すると、両方のコントローラーのセルはプロトタイプのように空でした。

繰り返しますが、これは予想されたことです。再利用識別子はストーリーボードシーンまたはペン先間で共有されないため、これらの個別のセルすべてに同じ再利用識別子があるという事実は無意味です。テーブルビューから取得したセルは、ストーリーボードのそのシーンのプロトタイプセルと一致する外観になります。

しかし、この解決策は近いものでした。お気づきのように-[UITableView registerNib:forCellReuseIdentifier:]、プログラムでを呼び出して、UINibセルを含むを渡すだけで、同じセルが返されます。(これはプロトタイプがnibを「オーバーライド」しているためではありません。単にnibをtableviewに登録していなかったため、ストーリーボードに埋め込まれたnibをまだ見ていました。)残念ながら、このアプローチには欠陥があります—スタンドアローンペン先のセルにストーリーボードセグエを接続する方法はありません。

両方のコントローラーのプロトタイプを空にしてクラスを設定し、IDをセルクラスに再利用します。セルのUIを完全にコードで構築しました。セルはすべてのコントローラーで完全に機能します。

当然。うまくいけば、これは驚くべきことではありません。


だから、それはそれが機能しなかった理由です。スタンドアロンのnibでセルをデザインし、複数のストーリーボードシーンで使用できます。現在、これらのセルにストーリーボードセグエを接続することはできません。うまくいけば、あなたはこれを読んでいる過程で何かを学んだことでしょう。


ああ、わかった。あなたは私の誤解を釘付けにしました—ビュー階層は私のクラスから完全に独立しています。振り返ってみると明らかです!すばらしい答えをありがとう。
クリフW

不可能ではなくなったようです。stackoverflow.com
リッチアポダカ2012年

7
@RichApodaca私は私の答えでその解決策を述べました。しかし、それはストーリーボードにはありません。別のペン先にあります。したがって、セグエを配線したり、他のストーリーボードのようなことをしたりすることはできませんでした。したがって、それは最初の質問を完全に扱うわけではありません。
BJホーマー

XCode8以降、ストーリーボードのみのソリューションが必要な場合は、次の回避策が機能するようです。ステップ1)ViewController#1のテーブルビューにプロトタイプセルを作成し、カスタムUITableViewCellクラスに関連付けます。ステップ2)そのセルをViewController#2のテーブルビューにコピーして貼り付けます。時間の経過とともに、ストーリーボードで作成したコピーを削除し、更新されたプロトタイプに貼り付けることにより、セルのコピーに更新を手動で伝達することを忘れないでください。
jengelsma

すばらしい回答ですが、フォローアップの質問があります。「ストーリーボードは、本質的には.xibファイルのコレクションにすぎません」これが当てはまる場合、なぜxibを埋め込むのが難しいのでしょうか。ストーリーボード?
willcwf

58

BJホーマーの素晴らしい答えにもかかわらず、私には解決策があるように感じます。私のテストに関する限り、それは機能します。

概念:xibセルのカスタムクラスを作成します。そこでタッチイベントを待って、プログラムでセグエを実行できます。ここで必要なのは、セグエを実行するコントローラーへの参照です。私の解決策は、それをに設定することですtableView:cellForRowAtIndexPath:

私がしているDetailedTaskCell.xib私は、複数のテーブルビューで使用したいテーブルのセルを含みます:

DetailedTaskCell.xib

TaskGuessTableCellそのセルにはカスタムクラスがあります。

ここに画像の説明を入力してください

ここで魔法が起こります。

// TaskGuessTableCell.h
#import <Foundation/Foundation.h>

@interface TaskGuessTableCell : UITableViewCell
@property (nonatomic, weak) UIViewController *controller;
@end

// TashGuessTableCell.m
#import "TaskGuessTableCell.h"

@implementation TaskGuessTableCell

@synthesize controller;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSIndexPath *path = [controller.tableView indexPathForCell:self];
    [controller.tableView selectRowAtIndexPath:path animated:NO scrollPosition:UITableViewScrollPositionNone];
    [controller performSegueWithIdentifier:@"FinishedTask" sender:controller];
    [super touchesEnded:touches withEvent:event];
}

@end

複数のセグエがありますが、すべて同じ名前です"FinishedTask"。ここで柔軟にする必要がある場合は、別のプロパティを追加することをお勧めします。

ViewControllerは次のようになります。

// LogbookViewController.m
#import "LogbookViewController.h"
#import "TaskGuessTableCell.h"

@implementation LogbookViewController

- (void)viewDidLoad
{
    [super viewDidLoad]

    // register custom nib
    [self.tableView registerNib:[UINib nibWithNibName:@"DetailedTaskCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"DetailedTaskCell"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TaskGuessTableCell *cell;

    cell = [tableView dequeueReusableCellWithIdentifier:@"DetailedTaskCell"];
    cell.controller = self; // <-- the line that matters
    // if you added the seque property to the cell class, set that one here
    // cell.segue = @"TheSegueYouNeedToTrigger";
    cell.taskTitle.text  = [entry title];
    // set other outlet values etc. ...

    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"FinishedTask"])
    {
        // do what you have to do, as usual
    }

}

@end

同じことを達成するためのよりエレガントな方法があるかもしれませんが、それはうまくいきます!:)


1
おかげで、私は自分のプロジェクトでこのアプローチを実装しています。代わりに、このメソッドをオーバーライドして、indexPathを取得して行を自分で選択する必要がないようにすることができます。if(選択)[self.controller performSegueWithIdentifier:self.segue sender:self]; } [super touchesEnded:touches withEvent:event];を呼び出すと、superがセルを選択すると思いました。そこにない場合、いつ選択されるか知っていますか?
thejaz

9
このソリューションでは、セル内でタッチが終了するたびにセグエをトリガーしていることに注意してください。これには、実際にセルを選択するのではなく、単にセルをスクロールする場合も含まれます。あなたはより良い運オーバーライドしている場合があります-setSelected:セルに、そしてから移行する場合にのみ、セグエをトリガーNOにしますYES
BJホーマー

setSelected:BJさん、私は幸運を祈っています。ありがとう。確かに、それは洗練されていない解決策です(それ間違っている感じます)が、同時に機能するので、これが修正されるまで(またはAppleの法廷で何かが変わるまで)使用しています。
ベンクリーガー、2012

16

これを探していたところ、Richard Venable がこの答えを見つけました。わたしにはできる。

iOS 5には、UITableViewに新しいメソッドが含まれています:registerNib:forCellReuseIdentifier:

これを使用するには、UITableViewCellをペン先に配置します。nibの唯一のルートオブジェクトである必要があります。

tableViewを読み込んだ後にnibを登録し、dequeueReusableCellWithIdentifier:をセル識別子とともに呼び出すと、Storyboardプロトタイプセルを使用した場合と同様に、nibからプルされます。


10

BJホーマーは、何が起こっているかについて優れた説明をしました。

実用的な見地から、セルをxibとして接続してセグエを接続できない場合、セルをxibとして選択するのが最善の選択です。複数の場所にわたるセルレイアウトやプロパティよりも遷移を維持する方がはるかに簡単です。 、とにかくあなたのセグエはあなたの異なるコントローラーとは異なる可能性があります。テーブルビューコントローラーから次のコントローラーに直接セグエを定義し、コードで実行できます。。

さらに、セルを個別のxibファイルとして持つと、アクションなどをテーブルビューコントローラーに直接接続できなくなる(とにかくこれを解決していません-ファイルの所有者を意味のあるものとして定義することはできません) )。セルのテーブルビューコントローラーが準拠すると予想されるプロトコルを定義し、コントローラーをデリゲートと同様に弱いプロパティとしてcellForRowAtIndexPathに追加することで、これを回避しています。


10

スウィフト3

BJホーマーは素晴らしい説明をしました、それは私が概念を理解するのを助けます。これはmake a custom cell reusable in storyboardmix the Storyboard and xibアプローチする必要があるすべてのTableViewControllerで使用できます。and CustomCellで使用されるasという名前のセルがあるTableViewControllerOneとしTableViewControllerTwoます。私は段階的にそれを作っています。
1.ファイル>新規>ファイルをクリック> Cocoa Touchクラスを選択>次へ>クラスの名前CustomCellを付ける(例: )> UITableVieCellとしてサブクラスを選択> XIBファイルも作成するチェックボックスをオンにして、次へを押します。
2.必要に応じてセルをカスタマイズし、セルの属性インスペクターで識別子を設定しCellIdentifierます。ここではと設定します。この識別子は、ViewControllerでセルを識別して再利用するために使用されます。
3.今、私たちはただしなければなりませんregister this cellViewControllerでviewDidLoad。初期化メソッドは必要ありません。
4.これで、このカスタムセルを任意のtableViewで使用できます。

TableViewControllerOne

let reuseIdentifier = "CellIdentifier"

override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: reuseIdentifier)
} 

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier:reuseIdentifier, for: indexPath) as! CustomCell
    return cell!
}

5

同じVCのセルをロードする方法を見つけましたが、セグエについてはテストしていません。これは、別のペン先でセルを作成するための回避策になる可能性があります

1つのVCと2つのテーブルがあり、ストーリーボードでセルをデザインして両方のテーブルで使用するとします。

(例:テーブルと結果のテーブルを含むUISearchControllerの検索フィールドで、両方に同じセルを使用したい場合)

コントローラがセルを要求するとき、これを行います:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString * identifier = @"CELL_ID";

    ContactsCell *cell = [self.YOURTABLEVIEW dequeueReusableCellWithIdentifier:identifier];
  // Ignore the "tableView" argument
}

そして、ここにストーリーボードからあなたの細胞があります


私はこれを試してみましたが、セルが再利用されることはありませんが、うまくいくようです。システムは常に新しいセルを作成して割り当てを解除します。
GilroyKilroy、2015年

これは、ストーリーボードを使用したテーブルビューへの検索バーの追加にある推奨事項と同じです。興味がある場合は、このソリューションの詳細な説明があります(を検索してくださいtableView:cellForRowAtIndexPath:)。
2015

これはあまりテキストであり、質問に答える
ジョアン・ヌネス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.