2016-04-22 17 views
1

Ich baue eine benutzerdefinierte UITableView mit benutzerdefinierten Zellen. Jede der benutzerdefinierten Zellen sind eine Unterklasse von FormItemTableViewCellSwift - Typ Casting

Ich bin versucht, die Zellendaten zu bevölkern in cellForRowAtIndexPath

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 

     var cell = FormItemTableViewCell(); 

     if(indexPath.row == 1){ 
      cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell 
     } else { 
      cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell 
     } 



     cell.questionLabel.text = "What is the meaning of life?"; 

     return cell 
    } 

Wie greife ich auf die Elemente in der Unterklasse?

Zum Beispiel: TwoOptionTableViewCell hat ein segControl während der OneTextFieldTableViewCell hat ein answerTextField

Antwort

2

Es gibt einige gute Antworten auf diese Frage, aber die meisten von ihnen gemeinsam eine schlechte Sache haben, sie zwingen ungeöffneten optionals, die Sie so viel vermeiden sollten, wie Sie können (so ziemlich das einzig akzeptable Ort, um sie zu verwenden ist in IBOutlets)

Dies ist der beste Weg, dies zu handhaben, was ich denke, ist:

ich
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 
    guard let cell = tableView.dequeueReusableCellWithIdentifier("Identifier", forIndexPath: indexPath) as? FormItemTableViewCell else { 
     fatalError("Cell is not of kind FormItemTableViewCell") 
    } 

    switch cell { 
    case let cell as TwoOptionTableViewCell where indexPath.row == 1: 
     // Configure cell, which is an object of class TwoOptionTableViewCell, but only when we are in row 1 
     break 
    case let cell as TwoOptionTableViewCell: 
     // Configure cell, which is an object of class TwoOptionTableViewCell, when the row is anything but 1 
     break 
    case let cell as OneTextFieldTableViewCell: 
     // Configure cell, which is an object of class OneTextFieldTableViewCell 
     break 
    case _: print("The cell \(cell) didn't match any patterns: \(indexPath)") 
    } 

    cell.questionLabel.text = "What is the meaning of life?"; 

    return cell 
} 

Jetzt gehen Sie, ich denke, es ist der beste Weg, durch die Gründe lassen.

Zunächst einmal, es nicht zwingt, alle Optionals auszupacken, alles ist schön im switch Fall ausgepackt.

Es entfernt Ihre Zelle aus der Tabelle (etwas, was Sie immer tun sollten) und stellt sicher, dass es eine Unterklasse von FormItemTableViewCell ist, andernfalls wird ein schwerwiegender Fehler ausgegeben.

Wenn Sie einen Switch-Fall verwenden, wird cell in die von Ihnen benötigte Klasse umgewandelt. Gleichzeitig wird überprüft, ob es sich um den gewünschten Indexpfad handelt. Wenn Sie also eine Logik in verschiedenen Zeilen teilen möchten, die eine Klasse teilen, können Sie indexPath.row mit mehreren Werten vergleichen. Wenn Sie die where-Klausel nicht verwenden, wird dieselbe Logik an allen Stellen verwendet, an denen eine Zelle mit dieser Klasse gefunden wird.

Beachten Sie, dass Sie einige Logik hinzufügen müssen, um den gewünschten Bezeichner abhängig von der Zeile zu erhalten.

+0

Ich mag die Switch-Anweisung Methode +1. Ist etwas falsch mit der Kraftauswicklung, wenn die Zelle aus der Warteschlange genommen wird? Ich sehe, dass Apples Dokumentation auch Kraftentfaltung ist. https://developer.apple.com/library/ios/referencelibrary/GettingStarted/DevelopiOSAppsSwift/Lesson7.html – Alan

+0

Force ausgepackte Optionals sollen eine letzte Möglichkeit sein, weil sie die Sicherheitskontrollen von Swift außer Kraft setzen (was einer davon ist) größten Funktionen) und kann Ihre App zum Absturz bringen, wenn Sie es nicht erwarten. In diesem Fall benutze ich 'fatalError', was auch deine App stürzt, aber es wird kontrolliert verwendet. Wenn du diesen Absturz siehst, weißt du, wo er herkommt und warum. – EmilioPelaez

3

Sie können eine der beiden Ansätze verwenden:

1) Der beste Weg:

if(indexPath.row == 1) { 
     let cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell 
     // the type of cell is TwoOptionTableViewCell. Configure it here. 
     return cell 
    } else { 
     let cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell 
     // the type of cell is TwoOptionTableViewCell. Configure it here. 
     return cell 
    } 

2) Wenn du cell nur einmal als Oberklasse deklarierst, dann musst du es so abwerten.

var cell: FormItemTableViewCell 

cell = ... // dequeue and assign the cell like you do in your code. 

if let twoOptionCell = cell as? TwoOptionTableViewCell 
{ 
    // configure twoOptionCell 
} 
else if let oneTextFieldCell = cell as? OneTextFieldTableViewCell 
{ 
    // configure oneTextFieldCell 
} 

return cell 

Dies ist ausführlicher, sobald Sie den Code hinzufügen, um die Zelle aus der Warteschlange zu entfernen. Also ich persönlich bevorzuge und empfehle den ersten Ansatz.

+0

Das ist ein guter Punkt, obwohl ich nicht gerne so viele Rückmeldungen in einer Methode habe, da ich mehr als 5 verschiedene Formularelemente betrachten könnte. – Alan

0

Sie müssen sie in diesem Fall bedingt werfen. Ich mag mit Aufzählungen für Zeilen/Abschnitte statt == 1 (je nachdem, wie Tableview ist Setup), aber im Grunde werden Sie Folgendes tun möchten:

if indexPath.row == 1 { 
    let cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell 
    // Note that we cast the cell to TwoOptionTableViewCell 
    // access `segControl` here 
    return cell 
} else { 
    let cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell 
    // This cell we cast to OneTextFieldTableViewCell. 
    // access `answerTextField` here 
    return cell 
} 

Was Sie taten, war die Zelle als FormItemTableViewCell definiert, Daher würden nachfolgende Zugriffe es nur in dieser Form kennen, obwohl Sie es während der Zuweisung explizit in eine Unterklasse umwandeln.

Als Nebenbemerkung müssen Sie die var nicht zuweisen, wie Sie dort getan haben, was Sie tun könnten, ist let cell: FormItemTableViewCell. Dann können Sie in den if-Anweisungen neue Zellen der Unterklassen definieren, auf ihnen operieren und dann zu Ihrem ursprünglichen cell zurückgeben und dann das zurückgeben. Dies ist nützlich, wenn Sie nach den if-Anweisungen dieselben Operationen für beide Zelltypen ausführen möchten (z. B. eine Hintergrundfarbe oder etwas festlegen, unabhängig davon, welche Unterklasse Sie haben).

Hier ist mein Favorit Weg, um diese Situation zu handhaben:

enum CellTypes { 
    case TwoOption, OneTextField 
    init(row: Int) { 
    if row == 1 { 
     self = .TwoOption 
    } else { 
     self = .OneTextField 
    } 
    } 

    var reuseIdentifier: String { 
    switch self { 
    case .TwoOption: return "twoOptionReuseIdentifier" 
    case .OneTextField: return "oneTextFieldReuseIdentifier" 
    } 
    } 
} 

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 
    let cell: FormItemTableViewCell 

    let cellType = CellTypes(row: indexPath.row) 
    switch cellType { 
    case .TwoOption: 
    let twoOptionCell = tableView.dequeueReusableCellWithIdentifier(cellType.reuseIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell 
    // do stuff with the `segControl` 
    cell = twoOptionCell 
    case .OneTextField: 
    let textFieldCell = tableView.dequeueReusableCellWithIdentifier(cellType.reuseIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell 
    // do stuff with the `answerTextField` 
    cell = textFieldCell 
    } 

    // Here do something regardless of which CellType it is: 
    cell.questionLabel.text = "What is the meaning of life?" 

    return cell 
} 
1

Wenn ich richtig verstehe, wollen Sie Haupterklärung Zelle als FormItemTableViewCell halten gemeinsame Eigenschaften zuzugreifen.

Sie können eine neue Variable erstellen und ihr die gegossene Version zuweisen. Mach deine Sachen mit dieser Instanz, da es sich um ein Klassenobjekt handelt, das auf dieselbe Referenz verweist.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 

    var cell = FormItemTableViewCell(); 
    // this can be replaced with below line as I don't see the purpose of creating an instance here while you use dequeue below. 
    // var cell: FormItemTableViewCell! 

    if(indexPath.row == 1){ 
     cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath); 
     let tempCell = cell as! TwoOptionTableViewCell; 
     // access members of TwoOptionTableViewCell on tempCell 
     tempCell.segControl.someProperty = 0; 
    } else { 
     cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath); 
     let tempCell = cell as! OneTextFieldTableViewCell; 
     // access members of OneTextFieldTableViewCell on tempCell 
     tempCell.answerTextField.text = "42"; 
    } 



    cell.questionLabel.text = "What is the meaning of life?"; 

    return cell 
} 
+0

danke! Ich bin froh, dass du den Sinn des Lebens kennst. – Alan