ios - 読み込み - xib 使用




XIBファイルからカスタムUITableViewCellをロードするにはどうすればいいですか? (16)

質問は簡単です:XIBファイルからカスタムUITableViewCellをロードするにはどうしたらいいですか? これにより、Interface Builderを使用してセルを設計することができます。 メモリ管理の問題のため、答えは明らかに単純ではありません。 このスレッドはこの問題について言及し、解決策を提案していますが、事前のNDAリリースであり、コードが不足しています。 ここでは、決定的な答えを提供せずに問題を議論する長いスレッドがあります。

私が使ったコードは次のとおりです:

static NSString *CellIdentifier = @"MyCellIdentifier";

MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil];
    cell = (MyCell *)[nib objectAtIndex:0];
}

このコードを使用するには、 UITableViewCell新しいサブクラスMyCell.m / .hを作成し、必要なコンポーネントのIBOutletsを追加します。 次に、新しい「Empty XIB」ファイルを作成します。 IBでXibファイルを開き、 UITableViewCellオブジェクトを追加し、その識別子を "MyCellIdentifier"に設定し、クラスをMyCellに設定してコンポーネントを追加します。 最後に、 IBOutletsをコンポーネントに接続します。 IBでFile's Ownerを設定していないことに注意してください。

他の方法では、Xibが追加のファクトリクラスを介してロードされていない場合、File's Ownerを設定し、メモリリークを警告します。 上記のInstruments / Leaksでテストしたところ、メモリリークは見られませんでした。

では、Xibsから細胞をロードする標準的な方法は何ですか? File's Ownerを設定しますか? 工場が必要ですか? もしそうなら、工場のコードはどうですか? 複数のソリューションがある場合は、それぞれの長所と短所を明確にしましょう...


登録

iOS 7以降、このプロセスは( スウィフト3.0まで )簡略化されました。

// For registering nib files
tableView.register(UINib(nibName: "MyCell", bundle: Bundle.main), forCellReuseIdentifier: "cell")

// For registering classes
tableView.register(MyCellClass.self, forCellReuseIdentifier: "cell")

)これは、 .stroyboardファイルまたは.stroyboardファイル内のセルをプロトタイプセルとして作成することによっても.stroyboardできます。 クラスをアタッチする必要がある場合は、セルプロトタイプを選択して対応するクラスを追加できます(もちろん、 UITableViewCell子孫でなければなりません)。

デキュー

後で、( スイフト3.0 )を使用してデキューします。

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
    let cell : UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)

    cell.textLabel?.text = "Hello"

    return cell
}

違いは、この新しいメソッドでは、セルをデキューするだけでなく、存在しないif (cell == nil) )、セルを次のように使用する準備ができていることを意味します。上記の例では、

tableView.dequeueReusableCell(withIdentifier:for:)tableView.dequeueReusableCell(withIdentifier:for:)は新しい動作を持ちますindexPath:ずに別のものを呼び出すと、古い動作を取得します。この動作では、 nilとインスタンスを自分で確認し、 UITableViewCell? 戻り値

if let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as? MyCellClass
{
    // Cell be casted properly
    cell.myCustomProperty = true
}
else
{
    // Wrong type? Wrong identifier?
}

もちろん、関連するクラスのクラスのタイプは、 UITableViewCellサブクラスの.xibファイルで定義したものです。あるいは、他のregisterメソッドを使用します。

構成

理想的には、セルを登録した時点で外観とコンテンツの配置(ラベルや画像ビューなど)が既に設定されており、単にcellForRowAtIndexPathメソッドで入力するだけです。

一緒に

class MyCell : UITableViewCell
{
    // Can be either created manually, or loaded from a nib with prototypes
    @IBOutlet weak var labelSomething : UILabel? = nil
}

class MasterViewController: UITableViewController 
{
    var data = ["Hello", "World", "Kinda", "Cliche", "Though"]

    // Register
    override func viewDidLoad()
    {
        super.viewDidLoad()

        tableView.register(MyCell.self, forCellReuseIdentifier: "mycell")
        // or the nib alternative
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
    {
        return data.count
    }

    // Dequeue
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
    {
        let cell = tableView.dequeueReusableCell(withIdentifier: "mycell", for: indexPath) as! MyCell

        cell.labelSomething?.text = data[indexPath.row]

        return cell
    }
}

もちろん、これはObjCで同じ名前で利用できます。


  1. UITableViewCellから独自のカスタムクラスAbcViewCellサブクラスを作成する(クラスファイル名とnibファイル名が同じであることを確認する)

  2. この拡張クラスメソッドを作成します。

    extension UITableViewCell {
        class func fromNib<T : UITableViewCell>() -> T {
            return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)?[0] as! T
        }
    }
    
  3. これを使って。

    let cell: AbcViewCell = UITableViewCell.fromNib()


NIBのリロードは高価です。 それを一度ロードし、セルが必要なときにオブジェクトをインスタンス化する方がよい。 この方法(Appleの "registerNIB" iOS5では1つのトップレベルオブジェクトのみが許可されています)を使用して、複数のセルであっても、nibにUIImageViewsなどを追加することができます(バグ10580062 "iOS5 tableView registerNib:

だから私のコードは以下の通りです - あなたはNIBを一度読み込みます(私がやったように初期化するか、viewDidload - 何であれ)。その後、ペン先をオブジェクトにインスタンス化し、必要なものを選択します。何度も。

static UINib *cellNib;

+ (void)initialize
{
    if(self == [ImageManager class]) {
        cellNib = [UINib nibWithNibName:@"ImageManagerCell" bundle:nil];
        assert(cellNib);
    }
}

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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    if(cell == nil) {
        NSArray *topLevelItems = [cellNib instantiateWithOwner:nil options:nil];
        NSUInteger idx = [topLevelItems indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
                            {
                                UITableViewCell *cell = (UITableViewCell *)obj;
                                return [cell isKindOfClass:[UITableViewCell class]] && [cell.reuseIdentifier isEqualToString:cellID];
                            } ];
        assert(idx != NSNotFound);
        cell = [topLevelItems objectAtIndex:idx];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"Howdie %d", indexPath.row];

    return cell;
}

Shawn Craverの答えを取り、ちょっときれいにしました。

BBCell.h:

#import <UIKit/UIKit.h>

@interface BBCell : UITableViewCell {
}

+ (BBCell *)cellFromNibNamed:(NSString *)nibName;

@end

BBCell.m:

#import "BBCell.h"

@implementation BBCell

+ (BBCell *)cellFromNibNamed:(NSString *)nibName {
    NSArray *nibContents = [[NSBundle mainBundle] loadNibNamed:nibName owner:self options:NULL];
    NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
    BBCell *customCell = nil;
    NSObject* nibItem = nil;
    while ((nibItem = [nibEnumerator nextObject]) != nil) {
        if ([nibItem isKindOfClass:[BBCell class]]) {
            customCell = (BBCell *)nibItem;
            break; // we have a winner
        }
    }
    return customCell;
}

@end

私は私のすべてのUITableViewCellのBBCellのサブクラスを作成し、標準を置き換えます

cell = [[[BBDetailCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"BBDetailCell"] autorelease];

with:

cell = (BBDetailCell *)[BBDetailCell cellFromNibNamed:@"BBDetailCell"];

XIBからカスタムセルを作成するために使ってきたクラスメソッドを次に示します。

+ (CustomCell*) createNewCustomCellFromNib {

    NSArray* nibContents = [[NSBundle mainBundle]
                            loadNibNamed:@"CustomCell" owner:self options:NULL];

    NSEnumerator *nibEnumerator = [nibContents objectEnumerator];
    CustomCell *customCell= nil;
    NSObject* nibItem = nil;

    while ( (nibItem = [nibEnumerator nextObject]) != nil) {

        if ( [nibItem isKindOfClass: [CustomCell class]]) {
            customCell = (CustomCell*) nibItem;

            if ([customCell.reuseIdentifier isEqualToString: @"CustomCell"]) {
                break; // we have a winner
            }
            else
                fuelEntryCell = nil;
        }
    }
    return customCell;
}

次に、XIBで、クラス名と再利用識別子を設定します。 その後、私はビューコントローラの代わりに、そのメソッドを呼び出すことができます

[[UITableViewCell] alloc] initWithFrame:]

それは十分に速く、私の2つの船積みのアプリケーションで使用されています。 [nib objectAtIndex:0]呼び出すよりも信頼性が高く、Steppan Burlotの例よりも信頼性が高いと思っています。XIBから正しいタイプのビューを取得することが保証されているからです。


元の著者状態がIBエンジニアによって推奨された 2つの方法があります。

詳細については、実際の投稿を参照してください。 私は方法#2を好む、それはより簡単に思える。

方法1:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
    if (cell == nil) {
        // Create a temporary UIViewController to instantiate the custom cell.
        UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:@"BDCustomCell" bundle:nil];
        // Grab a pointer to the custom cell.
        cell = (BDCustomCell *)temporaryController.view;
        [[cell retain] autorelease];
        // Release the temporary UIViewController.
        [temporaryController release];
    }

    return cell;
}

方法2:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
    if (cell == nil) {
        // Load the top-level objects from the custom cell XIB.
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil];
        // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
        cell = [topLevelObjects objectAtIndex:0];
    }

    return cell;
}

更新(2014):メソッド#2はまだ有効ですが、それ以上のドキュメントはありません。 以前は公式ドキュメントにあったが、現在はストーリーボードのために削除されている。

Githubに関する実例を投稿しました。
https://github.com/bentford/NibTableCellExample


ここでは、 UITableViewセルを登録するための普遍的なアプローチです:

protocol Reusable {
    static var reuseID: String { get }
}

extension Reusable {
    static var reuseID: String {
        return String(describing: self)
    }
}

extension UITableViewCell: Reusable { }

extension UITableView {

func register<T: UITableViewCell>(cellClass: T.Type = T.self) {
    let bundle = Bundle(for: cellClass.self)
    if bundle.path(forResource: cellClass.reuseID, ofType: "nib") != nil {
        let nib = UINib(nibName: cellClass.reuseID, bundle: bundle)
        register(nib, forCellReuseIdentifier: cellClass.reuseID)
    } else {
        register(cellClass.self, forCellReuseIdentifier: cellClass.reuseID)
    }
}

説明:

  1. Reusableプロトコルは、クラス名からセルIDを生成します。 cell ID == class name == nib name規約に従ってください。
  2. UITableViewCellReusableプロトコルに準拠しています。
  3. UITableViewエクステンションは、nibまたはクラスを介してセルを登録する際の差異を抽象化しUITableView

使用例:

override func viewDidLoad() {
    super.viewDidLoad()
    let tableView = UITableView()
    let cellClasses: [UITableViewCell.Type] = [PostCell.self, ProfileCell.self, CommentCell.self]
    cellClasses.forEach(tableView.register)
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: PostCell.self.reuseID) as? PostCell
    ...
    return cell
}

ここに私の方法です: XIBファイルからカスタムUITableViewCellsを読み込む...さらに別のメソッド

アイデアは、 IBOutlet UIView *contentプロパティと、コードから設定する必要がある各カスタムサブビューのプロパティをUITableViewCell SampleCellサブクラスを作成することです。 次に、SampleCell.xibファイルを作成します。 このnibファイルで、ファイル所有者をSampleCellに変更します。 あなたのニーズに合わせたサイズのコンテンツUIViewを追加してください。 必要なすべてのサブビュー(ラベル、イメージビュー、ボタンなど)を追加して設定します。 最後に、コンテンツビューとサブビューをファイル所有者にリンクします。


これをチェックする - http://eppz.eu/blog/custom-uitableview-cell/ - コントローラーの実装で1行になる小さなクラスを使用すると便利です。

-(UITableViewCell*)tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath
{
    return [TCItemCell cellForTableView:tableView
                          atIndexPath:indexPath
                      withModelSource:self];
}


これを行うには、コントローラクラスにIBOutlet UITableViewCell *cell宣言します。 その後、上記で宣言されたセルにUITableViewCellを供給するNSBundle loadNibNamedクラスメソッドを呼び出しUITableViewCell

xibについては、空のxibを作成し、必要に応じてセットアップできるIBでUITableViewCellオブジェクトを追加しUITableViewCell 。 このビューは、コントローラクラスのセルIBOutletに接続されます。

- (UITableViewCell *)tableView:(UITableView *)table
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"%@ loading RTEditableCell.xib", [self description] );

    static NSString *MyIdentifier = @"editableCellIdentifier";
    cell = [table dequeueReusableCellWithIdentifier:MyIdentifier];

    if(cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:@"RTEditableCell"
                                      owner:self
                                    options:nil];
    }

    return cell;
}

NSBundleの追加loadNibNamed(ADCログイン)

cocoawithlove.comの記事私はコンセプトからソースを取得しました(電話番号サンプルアプリケーションを入手してください)


正しい方法は、UITableViewCellサブクラスの実装、ヘッダー、およびXIBを作成することです。 XIBで任意のビューを削除し、単に表のセルを追加します。 クラスをUITableViewCellサブクラスの名前として設定します。 ファイル所有者の場合は、UITableViewControllerサブクラスのクラス名にします。 tableViewCellコンセントを使用して、ファイル所有者をセルに接続します。

ヘッダファイル:

UITableViewCell *_tableViewCell;
@property (assign) IBOutlet UITableViewCell *tableViewCell;

実装ファイル:

@synthesize tableViewCell = _tableViewCell;

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

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
    if (cell == nil) {
        [[NSBundle mainBundle] loadNibNamed:kCellIdentifier owner:self options:nil];
        cell = _tableViewCell;
        self.tableViewCell = nil;
    }

    return cell;
}

正しい解決策はこれです:

- (void)viewDidLoad
{
 [super viewDidLoad];
 UINib *nib = [UINib nibWithNibName:@"ItemCell" bundle:nil];
 [[self tableView] registerNib:nib forCellReuseIdentifier:@"ItemCell"];
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   // Create an instance of ItemCell
   PointsItemCell *cell =  [tableView dequeueReusableCellWithIdentifier:@"ItemCell"];

return cell;
}

正解はこれです

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"CustomCell"];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell  *cell = [tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
    return cell; 
    }

私はbentfordの方法#2を使用しました:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BDCustomCell"];
    if (cell == nil) {
        // Load the top-level objects from the custom cell XIB.
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"BDCustomCell" owner:self options:nil];
        // Grab a pointer to the first object (presumably the custom cell, as that's all the XIB should contain).
        cell = [topLevelObjects objectAtIndex:0];
    }

    return cell;
}

それは動作しますが、カスタムUITableViewCell .xibファイルのFile's Ownerへの接続については注意してください。

loadNibNamedステートメントでowner:selfを渡すことによって、 UITableViewController File's Ownerとして設定しUITableViewCell

IBのヘッダーファイルにドラッグ&ドロップしてアクションとアウトレットを設定すると、デフォルトでFile's Ownerとして設定されます。

loadNibNamed:owner:optionsでは、アップルのコードはあなたのUITableViewControllerプロパティを設定しようとします。なぜなら、それはオーナーだからです。 しかし、そこに定義されているプロパティは存在しないため、 キー値のコーディングに準拠しているというエラーが発生します

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason:     '[<MyUITableViewController 0x6a383b0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key myLabel.'

代わりにイベントがトリガされると、NSInvalidArgumentExceptionが発生します。

-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MyUITableViewController switchValueDidChange:]: unrecognized selector sent to instance 0x8e9acd0'
*** First throw call stack:
(0x1903052 0x15eed0a 0x1904ced 0x1869f00 0x1869ce2 0x1904ec9 0x5885c2 0x58855a 0x62db76 0x62e03f 0x77fa6c 0x24e86d 0x18d7966 0x18d7407 0x183a7c0 0x1839db4 0x1839ccb 0x1f8b879 0x1f8b93e 0x585a9b 0xb904d 0x2c75)
terminate called throwing an exceptionCurrent language:  auto; currently objective-c

簡単な回避策は、File's OwnerではなくUITableViewCellでInterface Builder接続をポイントすることです。

  1. ファイルの所有者を右クリックして、接続のリストをプルアップします。
  2. Command-Shift-4で画面をキャプチャする(キャプチャする領域をドラッグして選択する)
  3. ファイルの所有者からの接続を外します
  4. オブジェクト階層のUITableCellを右クリックし、接続を再追加します。

 NSString *CellIdentifier = [NSString stringWithFormat:@"cell %ld %ld",(long)indexPath.row,(long)indexPath.section];


    NewsFeedCell *cell = (NewsFeedCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    cell=nil;

    if (cell == nil)
    {
        NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"NewsFeedCell" owner:nil options:nil];

        for(id currentObject in topLevelObjects)
        {
            if([currentObject isKindOfClass:[NewsFeedCell class]])
            {
                cell = (NewsFeedCell *)currentObject;
                break;
            }
        }
}
return cell;

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

            let cellReuseIdentifier = "collabCell"
            var cell:collabCell! = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as? collabCell
            if cell == nil {
                tableView.register(UINib(nibName: "collabCell", bundle: nil), forCellReuseIdentifier: cellReuseIdentifier)
                cell = tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! collabCell!
            }


            return cell

}






xib