In uno storyboard, come posso creare una cella personalizzata da utilizzare con più controller?


216

Sto cercando di utilizzare gli storyboard in un'app a cui sto lavorando. Nell'app ci sono Elenchi e Utenti e ognuno contiene una raccolta dell'altro (membri di un elenco, elenchi di proprietà di un utente). Quindi, di conseguenza, ho ListCelle UserCelllezioni. L'obiettivo è quello di riutilizzarli in tutta l'app (ad es. In uno dei miei controller di tableview).

Ecco dove sto incontrando un problema.

Come posso creare una cella di visualizzazione tabella personalizzata nello storyboard che può essere riutilizzata in qualsiasi controller di visualizzazione?

Ecco le cose specifiche che ho provato finora.

  • Nel controller n. 1, ho aggiunto una cella prototipo, imposta la classe sulla mia UITableViewCellsottoclasse, imposta l'id di riutilizzo, ha aggiunto le etichette e le ha cablate ai punti vendita della classe. Nel controller n. 2, aggiunta una cella prototipo vuota, impostarla sulla stessa classe e riutilizzare l'id di prima. Quando viene eseguito, le etichette non vengono mai visualizzate quando le celle sono visualizzate nel Controller # 2. Funziona bene nel Controller # 1.

  • Progettato ogni tipo di cella in un NIB diverso e cablato alla classe di cella appropriata. Nello storyboard, ho aggiunto una cella prototipo vuota e impostato la sua classe e riutilizzando l'id per fare riferimento alla mia classe di cella. Nei viewDidLoadmetodi dei controller , ha registrato quei file NIB per l'id di riutilizzo. Quando mostrato, le celle in entrambi i controller erano vuote come il prototipo.

  • I prototipi conservati in entrambi i controller si svuotano e impostano la classe e riutilizzano l'id nella mia classe di cella. Costruito l'interfaccia utente delle celle interamente in codice. Le celle funzionano perfettamente in tutti i controller.

Nel secondo caso sospetto che il prototipo abbia sempre la precedenza sul NIB e se avessi ucciso le celle del prototipo, la registrazione del mio NIB per l'id di riutilizzo avrebbe funzionato. Ma poi non sarei in grado di impostare i follower dalle celle ad altri frame, che è davvero il punto centrale dell'utilizzo degli storyboard.

Alla fine, voglio due cose: collegare i flussi basati su tableview nello storyboard e definire i layout delle celle visivamente piuttosto che nel codice. Non riesco a vedere come ottenere entrambi finora.

Risposte:


205

A quanto ho capito, vuoi:

  1. Progetta una cella in IB che può essere utilizzata in più scene di storyboard.
  2. Configura segui storyboard unici da quella cella, a seconda della scena in cui si trova la cella.

Purtroppo, al momento non è possibile farlo. Per capire perché i tuoi precedenti tentativi non hanno funzionato, devi capire di più su come funzionano le storyboard e le celle di visualizzazione delle tabelle prototipo. (Se non ti interessa il motivo per cui questi altri tentativi non hanno funzionato, sentiti libero di andartene adesso. Non ho soluzioni magiche per te, oltre a suggerire di presentare un bug.)

Uno storyboard è, in sostanza, non molto più di una raccolta di file .xib. Quando carichi un controller di visualizzazione tabella che ha alcune celle prototipo da uno storyboard, ecco cosa succede:

  • Ogni cellula prototipo è in realtà il suo mini-pennino incorporato. Pertanto, quando il controller della vista tabella si sta caricando, scorre attraverso ciascuno dei pennini e delle chiamate della cella del prototipo -[UITableView registerNib:forCellReuseIdentifier:].
  • La vista tabella richiede al controller le celle.
  • Probabilmente chiami -[UITableView dequeueReusableCellWithIdentifier:]
  • Quando richiedi una cella con un determinato identificatore di riutilizzo, verifica se ha un pennino registrato. In tal caso, crea un'istanza di quella cella. Questo è composto dai seguenti passaggi:

    1. Guarda la classe della cella, come definita nel pennino della cella. Chiama [[CellClass alloc] initWithCoder:].
    2. Il -initWithCoder:metodo esegue e aggiunge viste secondarie e imposta le proprietà definite nel pennino. ( IBOutletProbabilmente anche io sarò collegato qui, anche se non l'ho provato; potrebbe succedere in -awakeFromNib)
  • Configura la tua cella come vuoi.

La cosa importante da notare qui è che c'è una distinzione tra la classe della cellula e l' aspetto visivo della cellula. Potresti creare due celle prototipo separate della stessa classe, ma con le loro visualizzazioni secondarie disposte in modo completamente diverso. In effetti, se usi gli UITableViewCellstili predefiniti , questo è esattamente ciò che sta accadendo. Lo stile "Predefinito" e lo stile "Sottotitoli", ad esempio, sono entrambi rappresentati dalla stessa UITableViewCellclasse.

Questo è importante : la classe della cella non ha una correlazione individuale con una particolare gerarchia di viste . La gerarchia della vista è determinata interamente da ciò che è nella cella del prototipo che è stata registrata con questo particolare controller.

Si noti inoltre che l'identificatore di riutilizzo della cella non è stato registrato in alcuni dispensari di celle globali. L'identificatore di riutilizzo viene utilizzato solo nel contesto di una singola UITableViewistanza.


Date queste informazioni, diamo un'occhiata a quello che è successo nei tuoi tentativi di cui sopra.

Nel controller n. 1, aggiunta una cella prototipo, imposta la classe sulla mia sottoclasse UITableViewCell, imposta l'id di riutilizzo, ha aggiunto le etichette e le ha cablate ai punti vendita della classe. Nel controller n. 2, aggiunta una cella prototipo vuota, impostarla sulla stessa classe e riutilizzare l'id di prima. Quando viene eseguito, le etichette non vengono mai visualizzate quando le celle sono visualizzate nel Controller # 2. Funziona bene nel Controller # 1.

Questo è previsto. Mentre entrambe le celle avevano la stessa classe, la gerarchia di vista passata alla cella in Controller n. 2 era completamente priva di sottoview. Quindi hai una cella vuota, che è esattamente ciò che hai inserito nel prototipo.

Progettato ogni tipo di cella in un NIB diverso e cablato alla classe di cella appropriata. Nello storyboard, ho aggiunto una cella prototipo vuota e impostato la sua classe e riutilizzando l'id per fare riferimento alla mia classe di cella. Nei metodi viewDidLoad dei controller, sono stati registrati quei file NIB per l'id di riutilizzo. Quando mostrato, le celle in entrambi i controller erano vuote come il prototipo.

Ancora una volta, questo è previsto. L'identificatore di riutilizzo non è condiviso tra scene dello storyboard o pennini, quindi il fatto che tutte queste celle distinte abbiano lo stesso identificatore di riutilizzo non ha senso. La cella che ritorni dalla vista tabella avrà un aspetto che corrisponde alla cella prototipo in quella scena dello storyboard.

Questa soluzione era vicina, comunque. Come hai notato, potresti semplicemente chiamare a livello di codice -[UITableView registerNib:forCellReuseIdentifier:], passando il UINibcontenitore della cella e otterrai quella stessa cella. (Questo non è perché il prototipo stava "scavalcando" il pennino; semplicemente non avevi registrato il pennino con la vista da tavolo, quindi stava ancora guardando il pennino incorporato nello storyboard.) Sfortunatamente, questo approccio presenta un difetto: non c'è modo di collegare i follower dello storyboard a una cella in un pennino autonomo.

I prototipi conservati in entrambi i controller si svuotano e impostano la classe e riutilizzano l'id nella mia classe di cella. Costruito l'interfaccia utente delle celle interamente in codice. Le celle funzionano perfettamente in tutti i controller.

Naturalmente. Speriamo che questo non sorprenda.


Quindi, ecco perché non ha funzionato. Puoi progettare le tue celle in pennini autonomi e usarle in più scene di storyboard; al momento non puoi semplicemente collegare i follower dello storyboard a quelle celle. Spero che tu abbia imparato qualcosa nel processo di lettura.


Ah, ho capito. Hai inchiodato il mio malinteso: la gerarchia di vedute è completamente indipendente dalla mia classe. Ovvio a posteriori! Grazie per la magnifica risposta.
Cliff W,

Non è più impossibile, sembra: stackoverflow.com/questions/8574188/…
Rich Apodaca,

7
@RichApodaca Ho menzionato quella soluzione nella mia risposta. Ma non è nello storyboard; è in un pennino separato. Quindi non puoi collegare follower o fare altre cose da storyboard. Quindi, non affronta totalmente la domanda iniziale.
BJ Homer,

A partire da XCode8 la seguente soluzione alternativa sembra funzionare se si desidera una soluzione solo storyboard. Passaggio 1) Creare la cella del prototipo in una vista tabella in ViewController # 1 e associarla alla classe UITableViewCell personalizzata Passaggio 2) Copia / Incolla quella cella nella vista tabella di ViewController # 2. Nel tempo dovrai ricordarti di propagare manualmente gli aggiornamenti alle copie della cella eliminando le copie che hai fatto nello storyboard e incollandole nel prototipo aggiornato.
jengelsma,

Ottima risposta, ho una domanda successiva:> "Uno storyboard è, in sostanza, non molto più di una raccolta di file .xib" se è così, perché è così difficile incorporare un xib in uno storyboard?
willcwf,

58

Nonostante la grande risposta di BJ Homer, sento di avere una soluzione. Per quanto riguarda i miei test, funziona.

Concetto: creare una classe personalizzata per la cella xib. Lì puoi aspettare un evento touch ed eseguire il seguito a livello di codice. Ora tutto ciò di cui abbiamo bisogno è un riferimento al controller che esegue il Segue. La mia soluzione è inserirla tableView:cellForRowAtIndexPath:.

Esempio

Ho una DetailedTaskCell.xibcella contenente una tabella che vorrei utilizzare in più viste tabella:

DetailedTaskCell.xib

Esiste una classe personalizzata TaskGuessTableCellper quella cella:

inserisci qui la descrizione dell'immagine

Qui è dove avviene la magia.

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

Ho più Segues ma tutti hanno lo stesso nome: "FinishedTask". Se devi essere flessibile qui, ti suggerisco di aggiungere un'altra proprietà.

ViewController è simile al seguente:

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

Potrebbero esserci modi più eleganti per ottenere lo stesso ma - funziona! :)


1
Grazie, sto implementando questo approccio nel mio progetto. Puoi invece sostituire questo metodo in modo da non dover ottenere indexPath e selezionare tu stesso la riga: - (void) setSelected: (BOOL) selezionato animato: (BOOL) animato {[super setSelected: selezionato animato: animato]; if (selezionato) [self.controller performSegueWithIdentifier: self.segue mittente: self]; } Ho pensato che super avrebbe selezionato la cella quando si chiamava [super touchesEnded: touch withEvent: event] ;. Sai quando è selezionato se non presente?
thejaz,

9
Nota che con questa soluzione, stai attivando il follow ogni volta che un tocco termina all'interno di una cella. Ciò include se stai semplicemente scorrendo la cella, non provando a selezionarla. Potresti avere maggiore fortuna scavalcando -setSelected:la cella e attivando il seguito solo durante la transizione da NOa YES.
BJ Homer,

Ho più fortuna con setSelected:, BJ. Grazie. In effetti, è una soluzione non elegante ( sembra sbagliata), ma allo stesso tempo funziona, quindi la sto usando fino a quando questo non viene risolto (o qualcosa cambia nel campo di Apple).
Ben Kreeger,

16

Lo stavo cercando e ho trovato questa risposta di Richard Venable. Per me funziona.

iOS 5 include un nuovo metodo su UITableView: registerNib: forCellReuseIdentifier:

Per usarlo, metti un UITableViewCell in un pennino. Deve essere l'unico oggetto root nel pennino.

Puoi registrare il pennino dopo aver caricato tableView, quindi quando chiami dequeueReusableCellWithIdentifier: con l'identificatore di cella, lo estrarrà dal pennino, proprio come se avessi usato una cellula prototipo di Storyboard.


10

BJ Homer ha fornito un'eccellente spiegazione di ciò che sta succedendo.

Da un punto di vista pratico aggiungerei che, dato che non puoi avere celle come xibs e connettere segues, la migliore da scegliere è avere la cella come xib - le transizioni sono molto più facili da mantenere rispetto ai layout e alle proprietà delle celle in più punti e comunque i tuoi follower saranno probabilmente diversi dai tuoi diversi controller. È possibile definire il seguito direttamente dal controller della vista tabella al controller successivo ed eseguirlo in codice. .

Un'ulteriore nota è che avere la tua cella come un file xib separato ti impedisce di essere in grado di connettere qualsiasi azione ecc. Direttamente al controller della vista tabella (non l'ho ancora capito, non puoi definire il proprietario del file come qualcosa di significativo ). Sto lavorando a questo proposito definendo un protocollo al quale il controller della vista tabella della cella dovrebbe conformarsi e aggiungendo il controller come proprietà debole, simile a un delegato, in cellForRowAtIndexPath.


10

Swift 3

BJ Homer ha dato un'ottima spiegazione, mi aiuta a capire il concetto. A make a custom cell reusable in storyboard, che può essere utilizzato in qualsiasi TableViewController dobbiamo mix the Storyboard and xibavvicinarci. Supponiamo di avere una cella chiamata come CustomCellche deve essere usata in TableViewControllerOnee TableViewControllerTwo. Lo sto facendo a passi.
1. File> Nuovo> Fai clic su File> Seleziona classe Cocoa Touch> fai clic su Avanti> Assegna nome della tua classe (ad esempio CustomCell)> seleziona Sottoclasse come UITableVieCell> Seleziona la casella di controllo Crea anche file XIB e premi Avanti.
2. Personalizza la cella come desideri e imposta l'identificatore nella finestra di ispezione degli attributi per cella, qui imposteremo come CellIdentifier. Questo identificatore verrà utilizzato nel ViewController per identificare e riutilizzare la cella.
3. Ora non ci resta cheregister this cellnel nostro ViewController viewDidLoad. Non è necessario alcun metodo di inizializzazione.
4. Ora possiamo usare questa cella personalizzata in qualsiasi tableView.

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

5

Ho trovato un modo per caricare la cella per lo stesso VC, non testato per i segues. Questa potrebbe essere una soluzione alternativa per la creazione della cella in un pennino separato

Diciamo che hai un VC e 2 tabelle e vuoi progettare una cella nello storyboard e usarla in entrambe le tabelle.

(es: una tabella e un campo di ricerca con un UISearchController con una tabella per i risultati e si desidera utilizzare la stessa cella in entrambi)

Quando il controller richiede la cella, procedere come segue:

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

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

E qui hai la tua cella dallo storyboard


Ho provato questo e sembra funzionare TUTTAVIA le celle non vengono mai riutilizzate. Il sistema crea e distribuisce sempre nuove celle ogni volta.
GilroyKilroy il

Questa è la stessa raccomandazione che si trova in Aggiunta di una barra di ricerca a una vista tabella con storyboard . Se sei interessato, c'è una spiegazione più approfondita di questa soluzione lì (cerca tableView:cellForRowAtIndexPath:).
Senso

ma questo è meno testo e risponde alla domanda
João Nunes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.