web-dev-qa-db-ja.com

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

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

それは私が問題に直面しているところです。

どのようにView Controllerで再利用できるカスタムTableviewセルをストーリーボードに作成しますか?

ここに、私がこれまでに試した具体的なものを示します。

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

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

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

2番目のケースでは、プロトタイプが常にNIBをオーバーライドしていると思われます。プロトタイプセルを削除した場合、再利用IDにNIBを登録すると機能します。しかし、その後、セルから他のフレームにセグエを設定することはできません。これは、ストーリーボードを使用する実際のポイントです。

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

216
Cliff W

私はそれを理解しているように、あなたがしたい:

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

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

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

  • 各プロトタイプセルは、実際には独自の組み込みミニニブです。したがって、Table View Controllerがロードされると、プロトタイプセルの各nibを介して実行され、-[UITableView registerNib:forCellReuseIdentifier:]が呼び出されます。
  • テーブルビューは、コントローラーにセルを要求します。
  • おそらく-[UITableView dequeueReusableCellWithIdentifier:]を呼び出します
  • 特定の再利用識別子を持つセルを要求すると、nibが登録されているかどうかがチェックされます。存在する場合、そのセルのインスタンスをインスタンス化します。これは、次の手順で構成されます。

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

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

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

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


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

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

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

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

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

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

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

当然。うまくいけば、これは当然のことです。


だから、それはうまくいかなかった理由です。スタンドアロンのペン先でセルを設計し、複数のストーリーボードシーンで使用できます。現在、これらのセルにストーリーボードセグエを接続することはできません。ただし、これを読んでいる過程で何かを学んだことを願っています。

204
BJ Homer

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

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

複数のテーブルビューで使用するテーブルセルを含むDetailedTaskCell.xibがあります。

DetailedTaskCell.xib

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

enter image description here

これは魔法が起こるところです。

// 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

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

58
ericteubert

私はこれを探していましたが、リチャード・ヴェナブルの この答え を見つけました。わたしにはできる。

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

それを使用するには、UITableViewCellをnibに入れます。これは、ペン先の唯一のルートオブジェクトでなければなりません。

TableViewをロードした後、nibを登録できます。セル識別子を使用してdequeueReusableCellWithIdentifier:を呼び出すと、Storyboardプロトタイプセルを使用した場合と同様に、nibからnibを取得します。

16
Odrakir

Swift

BJホーマーはすばらしい説明をしてくれました。コンセプトを理解するのに役立ちます。 TableViewControllerで使用できるmake a custom cell reusable in storyboardには、mix the Storyboard and xibアプローチが必要です。 CustomCellおよびTableViewControllerOneで使用されるTableViewControllerTwoという名前のセルがあるとします。私は段階的にそれを作っています。
1。 [ファイル]> [新規]> [ファイル]> [Cocoa Touchクラスを選択]> [次へ]>クラスの名前を指定します(たとえばCustomCell)> UITableVieCellとしてサブクラスを選択>ファイルのチェックボックスをオンにし、[次へ]を押します。
2。必要に応じてセルをカスタマイズし、セルの属性インスペクターで識別子を設定します。ここではCellIdentifierとして設定します。この識別子は、セルを識別して再利用するためにViewControllerで使用されます。
これで、ViewController viewDidLoadregister this cellを実行するだけです。初期化メソッドは必要ありません。
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!
}
10
Kunal Kumar

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

実用的な観点から、セルをxibとして使用できず、セグエを接続できないことを考えると、セルをxibとして選択するのが最良の選択です。 、とにかくセグエが異なるコントローラーと異なる可能性があります。テーブルビューコントローラーから次のコントローラーにセグエを直接定義し、コードで実行できます。 。

さらに注意すべき点は、セルを独立したxibファイルとして使用すると、アクションなどをTable View Controllerに直接接続できないことです(とにかくこれを解決していません-ファイルの所有者を意味のあるものとして定義することはできません)。セルのTable View Controllerが準拠することが期待されるプロトコルを定義し、cellForRowAtIndexPathのデリゲートに似た弱いプロパティとしてコントローラを追加することで、これを回避しています。

10
jrturton

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

1つのVCと2つのテーブルがあり、ストーリーボードでセルを設計し、両方のテーブルで使用したいとします。

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

コントローラーがセルを要求したら、次のようにします。

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

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

そして、ここにはストーリーボードからのセルがあります

5
João Nunes