ios länge - Wie laden Sie benutzerdefinierte UITableViewCells aus Xib-Dateien?




meta description (18)

  1. Erstellen Sie Ihre eigene benutzerdefinierte Klasse AbcViewCell Unterklasse von UITableViewCell ( UITableViewCell Sie sicher, dass der Klassendateiname und der Name der Nib-Datei identisch sind)

  2. Erstellen Sie diese Erweiterungsklassenmethode.

    extension UITableViewCell {
        class func fromNib<T : UITableViewCell>() -> T {
            return Bundle.main.loadNibNamed(String(describing: T.self), owner: nil, options: nil)?[0] as! T
        }
    }
    
  3. Benutze es.

    let cell: AbcViewCell = UITableViewCell.fromNib()

Die Frage ist einfach: Wie laden Sie benutzerdefinierte UITableViewCell aus Xib-Dateien? Auf diese Weise können Sie mit dem Interface Builder Ihre Zellen entwerfen. Die Antwort ist anscheinend nicht einfach aufgrund von Speicherverwaltungsproblemen. Dieser Thread erwähnt das Problem und schlägt eine Lösung vor, ist aber vor NDA-Release und Code fehlt. Hier ist ein langer Thread , der das Problem behandelt, ohne eine definitive Antwort zu geben.

Hier ist ein Code, den ich benutzt habe:

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

Um diesen Code zu verwenden, erstellen Sie MyCell.m / .h, eine neue Unterklasse von UITableViewCell und fügen Sie IBOutlets für die IBOutlets Komponenten hinzu. Erstellen Sie dann eine neue "Leere XIB" -Datei. Öffnen Sie die Xib-Datei in IB, fügen Sie ein UITableViewCell Objekt hinzu, legen Sie den Bezeichner auf "MyCellIdentifier" fest und legen Sie seine Klasse auf MyCell fest, und fügen Sie Ihre Komponenten hinzu. Abschließend verbinden Sie die IBOutlets mit den Komponenten. Beachten Sie, dass wir den Besitzer der Datei nicht in IB festgelegt haben.

Andere Methoden empfehlen, den Eigentümer der Datei festzulegen und vor Speicherlecks zu warnen, wenn der Xib nicht über eine zusätzliche Factory-Klasse geladen wird. Ich habe das oben unter Instrumente / Lecks getestet und sah keine Speicherlecks.

Was ist der übliche Weg, um Zellen von Xibs zu laden? Setzen wir den Eigentümer der Datei? Brauchen wir eine Fabrik? Wenn ja, wie sieht der Code für die Fabrik aus? Wenn es mehrere Lösungen gibt, lassen Sie uns die Vor- und Nachteile jedes einzelnen klären ...


Wenn Sie zum Erstellen von Zellen den Interface Builder verwenden, überprüfen Sie, ob Sie den Bezeichner im Bereich "Informationen" festgelegt haben. Überprüfen Sie dann, ob es beim Aufrufen von dequeueReusableCellWithIdentifier gleich ist.

Ich habe versehentlich vergessen, einige Identifikatoren in einem tabellenlastigen Projekt zu setzen, und die Leistungsänderung war wie Tag und Nacht.


Das Laden von UITableViewCells von XIBs spart eine Menge Code, führt aber normalerweise zu einer schrecklichen Scrollgeschwindigkeit (tatsächlich ist es nicht die XIB, sondern die übermäßige Verwendung von UIViews, die dies verursacht).

Ich schlage vor, Sie schauen sich das an: Verweis Verweis


Was ich dafür mache, ist eine IBOutlet UITableViewCell *cell in Ihrer Controller-Klasse zu deklarieren. NSBundle loadNibNamed dann die NSBundle loadNibNamed Klassenmethode auf, die die UITableViewCell der oben deklarierten Zelle UITableViewCell .

Für die Xib werde ich eine leere Xib erstellen und das UITableViewCell Objekt in IB hinzufügen, wo es nach Bedarf eingerichtet werden kann. Diese Ansicht wird dann mit der Zelle IBOutlet in der Controller-Klasse verbunden.

- (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-Zusätze loadNibNamed (ADC-Login)

ccoaawithlove.com article Ich habe das Konzept von (erhalten Sie die Telefonnummern Beispiel App)


Die richtige Lösung ist dies:

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

 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;

Ich habe Bentfords Methode # 2 benutzt :

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

Es funktioniert, aber achten Sie auf Verbindungen zu Dateibesitzer in Ihrer benutzerdefinierten UITableViewCell .xib-Datei.

Durch die Übergabe von owner:self in Ihrer loadNibNamed Anweisung legen Sie den UITableViewController als Besitzer der Datei Ihrer UITableViewCell .

Wenn Sie Drag & Drop in die Header-Datei in IB ziehen, um Aktionen und Outlets einzurichten, werden sie standardmäßig als Besitzer der Datei eingerichtet.

In loadNibNamed:owner:options wird Apples Code versuchen, Eigenschaften auf Ihrem UITableViewController , da dies der Eigentümer ist. Da diese Eigenschaften jedoch nicht definiert sind, erhalten Sie einen Fehler bezüglich der Schlüsselwertcodierung :

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

Wenn stattdessen ein Event ausgelöst wird, erhalten Sie eine 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

Eine einfache Problemumgehung besteht darin, Ihre Interface Builder-Verbindungen auf die UITableViewCell anstatt auf den UITableViewCell zu verweisen:

  1. Klicken Sie mit der rechten Maustaste auf den Besitzer der Datei, um die Liste der Verbindungen aufzurufen
  2. Nehmen Sie eine Bildschirmaufnahme mit Befehl-Umschalt-4 auf (ziehen Sie, um den zu erfassenden Bereich auszuwählen)
  3. x die Verbindungen vom Eigentümer der Datei aus
  4. Klicken Sie mit der rechten Maustaste auf die UITableCell in der Objekthierarchie und fügen Sie die Verbindungen erneut hinzu.

Registrieren

Nach iOS 7 wurde dieser Prozess auf ( swift 3.0 ) vereinfacht:

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

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

( Hinweis ) Dies ist auch möglich, indem Sie die Zellen in den .xib oder .stroyboard Dateien als Prototypzellen erstellen. Wenn Sie eine Klasse anhängen müssen, können Sie den UITableViewCell auswählen und die entsprechende Klasse hinzufügen (muss natürlich ein Nachkomme von UITableViewCell ).

Auspacken

Und später mit ( swift 3.0 ) aus der Warteschlange genommen:

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

    cell.textLabel?.text = "Hello"

    return cell
}

Der Unterschied ist, dass diese neue Methode nicht nur die Zelle aus der Warteschlange nimmt, sondern auch, wenn sie nicht existiert (das bedeutet, dass Sie keine if (cell == nil) Shenanigans machen müssen) und die Zelle bereit ist, genau so zu verwenden im obigen Beispiel.

( Warnung ) tableView.dequeueReusableCell(withIdentifier:for:) hat das neue Verhalten, wenn Sie das andere (ohne indexPath: tableView.dequeueReusableCell(withIdentifier:for:) aufrufen, erhalten Sie das alte Verhalten, in dem Sie nach nil UITableViewCell? und es selbst instanziieren müssen, beachten Sie die UITableViewCell? Rückgabewert.

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

Und natürlich ist der Typ der zugehörigen Klasse der Zelle der, den Sie in der .xib-Datei für die UITableViewCell Unterklasse definiert haben, oder alternativ die andere Registermethode.

Aufbau

Idealerweise wurden Ihre Zellen bereits hinsichtlich der Darstellung und der Positionierung der Inhalte (wie Beschriftungen und cellForRowAtIndexPath ) bereits bei der Registrierung cellForRowAtIndexPath , und bei der Methode cellForRowAtIndexPath füllen Sie sie einfach aus.

Alle zusammen

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

Und natürlich ist dies alles in ObjC mit den gleichen Namen verfügbar.


Die richtige Vorgehensweise besteht darin, eine UITableViewCell-Unterklassenimplementierung, einen Header und eine XIB zu erstellen. Entfernen Sie in der XIB alle Ansichten und fügen Sie einfach eine Tabellenzelle hinzu. Legen Sie die Klasse als den Namen der UITableViewCell-Unterklasse fest. Legen Sie für den Dateibesitzer den Klassennamen der UITableViewController-Klasse fest. Verbinden Sie den Dateibesitzer mit der Zelle tableViewCell mit der Zelle.

In der Header-Datei:

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

In der Implementierungsdatei:

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

Das Neuladen der NIB ist teuer. Besser, es einmal zu laden, dann instanziieren Sie die Objekte, wenn Sie eine Zelle benötigen. Beachten Sie, dass Sie mit dieser Methode UIImageViews etc. zur Nib hinzufügen können, sogar mehrere Zellen. (Apples registerNIB iOS5 erlaubt nur ein Objekt der obersten Ebene - Bug 10580062 "iOS5 tableView registerNib: übermäßig restriktiv"

Also mein Code ist unten - du liest in der NIB einmal (in Initialisieren wie ich oder in ViewDidload - was auch immer. Von da an instanzierst du die Spitze in Objekte und wählst dann die, die du brauchst. Das ist viel effizienter als das Laden der Spitze über und über.

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

Richtige Lösung ist das

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

Hier ist ein universeller Ansatz zum Registrieren von Zellen in 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)
    }
}

Erläuterung:

  1. Reusable Protokoll generiert die Zellen-ID Reusable seines Klassennamens. Stelle sicher, dass du der Konvention cell ID == class name == nib name : cell ID == class name == nib name .
  2. UITableViewCell entspricht dem Reusable Protokoll.
  3. UITableView Erweiterung abstrahiert den Unterschied bei der Registrierung von Zellen über Nib oder Klasse.

Verwendungsbeispiel:

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
}

Ich weiß nicht, ob es einen kanonischen Weg gibt, aber hier ist meine Methode:

  • Erstellen Sie eine Xib für einen ViewController
  • Legen Sie die Dateibesitzerklasse auf UIViewController fest
  • Löschen Sie die Ansicht und fügen Sie eine UITableViewCell hinzu
  • Setzen Sie die Klasse Ihrer UITableViewCell auf Ihre benutzerdefinierte Klasse
  • Legen Sie den Bezeichner Ihrer UITableViewCell fest
  • Stellen Sie den Ausgang Ihrer View-Controller-Ansicht auf Ihre UITableViewCell ein

Und benutze diesen Code:

MyCustomViewCell *cell = (MyCustomViewCell *)[_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
  UIViewController* c = [[UIViewController alloc] initWithNibName:CellIdentifier bundle:nil];
  cell = (MyCustomViewCell *)c.view;
  [c release];
}

In Ihrem Beispiel verwenden

[nib objectAtIndex:0]

kann brechen, wenn Apple die Reihenfolge der Elemente in der XIB ändert.


Importieren Sie zunächst Ihre benutzerdefinierte Zelle #import "CustomCell.h" und ändern Sie dann die Delegate-Methode wie #import "CustomCell.h" :

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

static NSString *simpleTableIdentifier = @"CustomCell";

CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
    NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"CustomCell" owner:self options:nil];
    cell = [nib objectAtIndex:0];

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
}         

     return cell;
}

Überprüfen Sie diese - http://eppz.eu/blog/custom-uitableview-cell/ - wirklich bequeme Art und Weise mit einer kleinen Klasse, die eine Zeile in Controller-Implementierung endet:

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


Nahm Shawn Cravers Antwort und räumte ein bisschen auf.

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

Ich mache alle meine UITableViewCells Unterklassen von BBCell und ersetze dann den Standard

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

mit:

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

Hier ist die Klassenmethode, die ich zum Erstellen von benutzerdefinierten Zellen aus XIBs verwendet habe:

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

Dann setze ich in der XIB den Klassennamen und benutze die Kennung erneut. Danach kann ich diese Methode einfach in meinem View - Controller statt in der

[[UITableViewCell] alloc] initWithFrame:]

Es ist schnell genug und wird in zwei meiner Versandanwendungen verwendet. Es ist zuverlässiger als das Aufrufen von [nib objectAtIndex:0] , und meiner Meinung nach zumindest zuverlässiger als das Beispiel von Stephan Burlot, weil Sie garantiert nur eine Ansicht aus einem XIB herausholen, der der richtige Typ ist.


Die Antwort, die Sie suchen, besteht darin, dass Sie beim Erstellen der Zertifikatsanforderung für GEMEINSAMEN NAMEN Ihren MENSCHLICHEN NAMEN wie in John Smith eingeben müssen, nicht Ihren App-Namen. Apple verwendet diesen Begriff Common Name auf zwei verschiedene Arten und sagt Ihnen nicht, dass das Zertifikat Ihren Namen darauf haben soll, während sie in einem anderen Bereich nach einem gemeinsamen Namen fragen und den APP-Namen haben wollen.







ios cocoa-touch uitableview