UITableViewを使用して1つのXIBでセルプロトタイプを設計する

そして同時に、セルの高さの自動計算の問題を解決します。



免責事項:

おそらく、この方法は最高のパフォーマンスを提供しません。ある程度の落とし穴があり、めまい、吐き気、悪魔を引き起こす可能性があります。高齢者や妊娠中の子供には読んでいないでください。



作成困難を克服することは、おそらくプログラマーにとって最大の動機です。 Xcodeには多くの欠陥、不透明なソリューション、バグが含まれています。 今日、私はそれらのいずれかの解決策を見つけようとします







iOS5で、彼らは素晴らしい機能を導入しました-ストーリーボードと、作成中のテーブル内でセルを直接プロトタイプする機能(ただし、個別のNIBにコンパイルされます)。 ただし、彼らは新しい機能を通常のXIBに導入しないことにしました。



新鮮なXcodeでは、UITableViewControllerを作成して、すぐにテーブル、さらにはセルのプロトタイプを作成できることに少し戸惑いました。 ただし、Xcodeをコンパイルすると、これを実行できないというエラーが表示されます。



そこで、 「なぜ?」という質問に答えました



2つの大きなストーリーボードがあるとしましょう。 なぜ2つ? 大量のビュー、ボタン、タブレットを1つに押し込めば、まったく新しい(1年前の)MacBook Pro Retina 13でさえ「カボチャに変わる」からです。



そのため、2つのストーリーボードがあります。たとえば、両方とも異なる状況で同じコントローラーを開く必要があります。



好奇心reader盛な読者は、iOS9で外部のストーリーボードへのリンクを導入したことに気付くでしょうが、プロジェクトでiOS8または7のサポートが必要な場合はどうでしょうか? 残念ながら、クパチーノの人たちは後方互換性を追加することを好みません。



可能な解決策は、ViewViewを使用せずにViewControllerを作成し、同じクラス名の外部Xibを作成して、loadViewメソッドで自動的にロードすることです。 または、nibNameプロパティを明示的に設定します。





以前はこのための明示的なフィールドがあったようですが、Xcode 7では見つかりませんでした。



そしてここで、テーブルのセルのプロトタイプを外部Xibに一度に1つずつファイルに入れ、それらをコードに明示的に登録する必要があるという問題に直面しています。 ストーリーボードを使用した後、私はそれをする気が全くありません。



そして、ここにあなたができることを示します(後で黒魔術が使用されるため、コードはObjective-Cになります)。



次の内容の.hファイルを作成します。

@interface UITableView (XibCells) @property (nonatomic, strong) IBOutletCollection(UITableViewCell) NSArray* cellPrototypes; @end
      
      





これにより、セルをInterface Builderファイル(テーブルの隣ではなく、隣)にドロップし、IBOutletCollection cellPrototypesに接続できます。







ここで、必要に応じてロードされるように、ロードされたセルを何らかの方法でテーブルにパームアウトする必要があります。

これを行うには、.mファイルにデータがプリロードされたUINib継承者を作成し、 instantiateWithOwner:optionsメソッドをオーバーライドします。

 @interface PrepopulatedNib: UINib @property (nonatomic, strong) NSData* nibData; @end @implementation PrepopulatedNib + (instancetype)nibWithObjects:(NSArray*)objects { PrepopulatedNib* nib = [[self alloc] init]; nib.nibData = [NSKeyedArchiver archivedDataWithRootObject:objects]; return nib; } - (NSArray *)instantiateWithOwner:(id)ownerOrNil options:(NSDictionary *)optionsOrNil { return [NSKeyedUnarchiver unarchiveObjectWithData:_nibData]; } @end
      
      





PrepopulatedNibオブジェクトが初期化されると、転送された配列はNSKeyedArchiverを使用してNSDataにアーカイブされます。

次に、UITableViewはinstantiateWithOwner:nil options:nilメソッドを呼び出し、配列を解凍して、オブジェクトのコピーを作成します。 この方法で取得されたセルは、NIBから解凍されたばかりでNSCodingプロトコルに準拠しているため、100%同一です。



最後の仕上げ:渡されたセルとPrepopulatedNibをテーブルにバインドします:

 @implementation UITableView (XibCells) - (void)setCellPrototypes:(NSArray*)cellPrototypes { for (UITableViewCell* cell in cellPrototypes) { [self registerNib:[PrepopulatedNib nibWithObjects:@[cell]] forCellReuseIdentifier:cell.reuseIdentifier]; } } @end
      
      





これで、テーブルはストーリーボードからロードされたかのように機能します。 ここでは少し混乱する可能性があり、最初の呼び出しで配列に転送された元のセルオブジェクトを返すので、リソースは無駄になりませんが、後で便利になります。



したがって、セルの高さの自動計算について

iOS8では、レイアウト制約を使用した独創的なセルの高さ計算がついに導入されました(多くのバグがありましたが)。 iOS9では、この機能が洗練され、スタックビューが追加されました。 繰り返しになりますが、下位互換性の問題はありません。



1つのXIBからセルをロードするコードを使用して、この問題の便利な解決策を提案します



高さを計算する1つの方法は、制約がインストールされた1つの非表示UITableViewCellインスタンスを保存することです。 これを行うには、プロシージャtableView:heightForRowAtIndexPath:のこのインスタンスで、将来のセルのアイテム/テキストが設定され、 [cell layoutIfNeeded]メソッドを呼び出した後、 cell.frame.size.heightが返されます。



このメソッドにはプリロードされたセルを使用します。 これを行うために、テーブルに関連付けられたNSDictionaryにセルを保存します。 これを行うには、.mファイルに指示を追加します
 #import <objc/runtime.h>
      
      





setCellPrototypes:メソッドで、セルを使用してNSDictionaryを作成します。キーは、reuseIdentifierです。

 @implementation UITableView (XibCells) static char cellPrototypesKey; - (void)setCellPrototypes:(NSArray<UITableViewCell *> *)cellPrototypes { NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:cellPrototypes.count]; for (UITableViewCell* cell in cellPrototypes) { [self registerNib:[PrepopulatedNib nibWithObjects:@[cell]] forCellReuseIdentifier:cell.reuseIdentifier]; dict[cell.reuseIdentifier] = cell; } objc_setAssociatedObject(self, &cellPrototypesKey, cellPrototypes, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSArray*)cellPrototypes { return nil; } //   warning-a - (UITableViewCell *)cellPrototypeWithIdentifier:(NSString *)reuseIdentifier { NSDictionary* dict = (NSDictionary*)objc_getAssociatedObject(self, &cellPrototypesKey); return dict[reuseIdentifier]; } @end
      
      





cellPrototypeWithIdentifier:宣言は、コードで使用できるように.hファイルに配置する必要があります。

 @interface UITableView (XibCells) @property (nonatomic, strong) IBOutletCollection(UITableViewCell) NSArray* cellPrototypes; - (UITableViewCell*)cellPrototypeWithIdentifier:(NSString*)reuseIdentifier; @end
      
      





これで、データソースコードでプロトタイプを使用して高さを計算できます。

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { id cellItem = _items[indexPath.section][indexPath.row]; MyTableViewCell* cell = [tableView cellPrototypeWithIdentifier:@"Cell"]; cell.item = cellItem; [cell layoutIfNeeded]; return cell.frame.size.height; }
      
      







意図的なコードは、概念実証であり、情報提供のみを目的として提供されているため、オールインワンソリューションを構成しません。



ご清聴ありがとうございました。



All Articles