Una cella di visualizzazione tabella non conosce la vista tabella a cui appartiene e va bene. In effetti, è così che dovrebbe essere. Tuttavia, le persone che sono nuove a questo concetto sono spesso confuse da esso. Ad esempio, se l'utente tocca un pulsante in una cella di visualizzazione tabella, come si ottiene il percorso dell'indice della cella in modo da poter recuperare il modello corrispondente? In questo tutorial, ti mostrerò come non farlo, come è fatto di solito e come farlo con stile ed eleganza.
Quando l'utente tocca una cella di visualizzazione tabella, la vista tabella viene richiamata tableView: didSelectRowAtIndexPath:
del UITableViewDelegate
protocollo sul delegato della vista tabella. Questo metodo accetta due argomenti, la vista tabella e il percorso dell'indice della cella selezionata.
Il problema che affronteremo in questo tutorial, tuttavia, è un po 'più complesso. Supponiamo di avere una vista tabella con celle, con ogni cella contenente un pulsante. Toccando il pulsante, viene attivata un'azione. Nell'azione, dobbiamo recuperare il modello che corrisponde alla posizione della cella nella vista tabella. In altre parole, dobbiamo conoscere il percorso dell'indice della cella. Come si deduce il percorso dell'indice della cella se si ottiene solo un riferimento al pulsante che è stato toccato? Questo è il problema che risolveremo in questo tutorial.
Crea un nuovo progetto in Xcode selezionando il Applicazione vista singola modello dalla lista di Applicazione iOS modelli. Assegna un nome al progetto Blocchi e celle, impostato dispositivi a i phone, e fare clic Il prossimo. Dillo a Xcode dove desideri archiviare il progetto e premi Creare.
Apri il Project Navigator a sinistra, seleziona il progetto nel Progetto sezione e impostare il Obiettivo di distribuzione a iOS 6. Lo facciamo per assicurarci di poter eseguire l'applicazione su iOS 6 e iOS 7. La ragione di ciò risulterà chiara più avanti in questo tutorial.
UITableViewCell
sottoclasseSelezionare Nuovo> File ... dal File menu e scegliere Classe Objective-C dalla lista di Cocoa Touch modelli. Dai un nome alla classe TPSButtonCell
e assicurarsi che erediti da UITableViewCell
.
Aprire il file di intestazione della classe e dichiarare due punti vendita, a UILabel
istanza chiamata titleLabel
e a UIButton
istanza chiamata ActionButton
.
#importare@interface TPSButtonCell: UITableViewCell @property (weak, nonatomic) IBOutlet UILabel * titleLabel; @property (weak, nonatomic) IBOutlet UIButton * actionButton; @fine
Apri il file di intestazione del file TPSViewController
classe e creare uno sbocco chiamato tableView
di tipo UITableView
. Il TPSViewController
ha anche bisogno di adottare il UITableViewDataSource
e UITableViewDelegate
protocolli.
#importare@interface TPSViewController: UIViewController @property (weak, nonatomic) IBOutlet UITableView * tableView; @fine
Dobbiamo anche dare una breve occhiata al file di implementazione del controller della vista. Aprire TPSViewController.m e dichiarare una variabile statica di tipo NSString
che useremo come identificatore di riutilizzo per le celle nella vista tabella.
#import "TPSViewController.h" @implementation TPSViewController static NSString * CellIdentifier = @ "CellIdentifier"; //… // @fine
Apri lo storyboard principale del progetto, Main.Storyboard e trascina una vista tabella nella vista del controller della vista. Seleziona la vista tabella e collegala fonte di dati
e delegare
punti vendita con l'istanza del controller di visualizzazione. Con la vista tabella ancora selezionata, apri il Ispettore degli attributi e impostare il numero di Prototipo di cellule a 1
. Il Soddisfare attributo deve essere impostato su Prototipi dinamici. Ora dovresti vedere una cella prototipo nella vista tabella.
Seleziona la cella prototipo e impostala Classe a TPSButtonCell
nel Identity Inspector. Con la cella ancora selezionata, apri il Ispettore degli attributi e impostare il Stile attribuire a costume e il Identifier a CellIdentifier.
Trascina a UILabel
istanza dal Libreria di oggetti alla visualizzazione del contenuto della cella e ripetere questo passaggio per a UIButton
esempio. Seleziona la cella, apri il Connections Inspector, e connetti il titleLabel
e ActionButton
prese con le loro controparti nella cella prototipo.
Prima di tornare al codice, dobbiamo creare un'altra connessione. Seleziona il controller di visualizzazione, apri il Connections Inspector ancora una volta e collegare il controller della vista tableView
presa con la vista tabella nello storyboard. Questo è tutto per l'interfaccia utente.
Inseriamo la vista tabella con alcuni film importanti che sono stati rilasciati nel 2013. Nel TPSViewController
classe, dichiara una proprietà di tipo NSArray
e nominalo fonte di dati
. La variabile di istanza corrispondente manterrà i film che mostreremo nella vista tabella. Popolare fonte di dati
con una dozzina circa di film nel controller della vista viewDidLoad
metodo.
#import "TPSViewController.h" @interface TPSViewController () @property (strong, nonatomic) NSArray * dataSource; @fine
- (void) viewDidLoad [super viewDidLoad]; // Imposta origine dati self.dataSource = @ [@ @ "titolo": @ "Gravity", @ "anno": @ (2013), @ @ "titolo": @ "12 anni schiavo", @ "anno": @ (2013), @ @ "titolo": @ "Prima di mezzanotte", @ "anno": @ (2013), @ @ "titolo": @ "American Hustle", @ "anno ": @ (2013), @ @" titolo ": @" Blackfish ", @" anno ": @ (2013), @ @" titolo ": @" Capitan Phillips ", @" anno ": @ (2013), @ @ "titolo": @ "Nebraska", @ "anno": @ (2013), @ @ "titolo": @ "Rush", @ "anno": @ (2013) , @ @ "title": @ "Frozen", @ "year": @ (2013), @ @ "title": @ "Star Trek Into Darkness", @ "anno": @ (2013), @ @ "title": @ "The Conjuring", @ "year": @ (2013), @ @ "title": @ "Effetti collaterali", @ "anno": @ (2013), @ @ "title": @ "The Attack", @ "year": @ (2013), @ @ "title": @ "Lo Hobbit", @ "anno": @ (2013), @ @ " title ": @" We Are What We Are ", @" year ": @ (2013), @ @" title ": @" Something in the Air ", @" year ": @ (2013)];
UITableViewDataSource
ProtocolloL'implementazione del UITableViewDataSource
il protocollo è molto semplice. Dobbiamo solo implementare numberOfSectionsInTableView:
, tableView: numberOfRowsInSection:
, e tableView: cellForRowAtIndexPath:
.
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView return self.dataSource? 1: 0; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section return self.dataSource? self.dataSource.count: 0; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * cell = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath: indexPath]; // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Configura cella Visualizza tabella [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", elemento [@ "titolo"], elemento [@ "anno"]]]; [cell.actionButton addTarget: self action: @selector (didTapButton :) forControlEvents: UIControlEventTouchUpInside]; cella di ritorno;
Nel tableView: cellForRowAtIndexPath:
, usiamo lo stesso identificatore che abbiamo impostato nello storyboard principale, CellIdentifier
, che abbiamo dichiarato in precedenza nel tutorial. Lanciamo la cella su un'istanza di TPSButtonCell
, recupera l'elemento corrispondente dall'origine dati e aggiorna l'etichetta del titolo della cella. Aggiungiamo anche un obiettivo e un'azione per il UIControlEventTouchUpInside
evento del pulsante.
Non dimenticare di aggiungere una dichiarazione di importazione per TPSButtonCell
classe nella parte superiore di TPSViewController.m.
#import "TPSButtonCell.h"
Per evitare l'arresto anomalo dell'applicazione quando viene toccato un pulsante, attuare didTapButton:
come mostrato di seguito.
- (void) didTapButton: (id) sender NSLog (@ "% s", __PRETTY_FUNCTION__);
Costruisci il progetto ed eseguilo in iOS Simulator per vedere cosa abbiamo ottenuto finora. Dovresti visualizzare un elenco di film e toccare il pulsante a destra per registrare un messaggio sulla console Xcode. Grande. È tempo per la carne del tutorial.
Quando l'utente tocca il pulsante a destra, invierà un messaggio di didTapButton:
al controller della vista. È quasi sempre necessario conoscere il percorso dell'indice della cella di visualizzazione tabella in cui si trova il pulsante. Ma come si ottiene il percorso dell'indice? Come ho detto, ci sono tre approcci che puoi adottare. Diamo prima un'occhiata a come non farlo.
Dai un'occhiata all'implementazione di didTapButton:
e prova a scoprire cosa c'è che non va. Hai notato il pericolo? Lascia che ti aiuti. Esegui l'applicazione prima su iOS 7 e poi su iOS 6. Dai un'occhiata a quali uscite Xcode alla console.
- (void) didTapButton: (id) sender // Trova tabella Visualizza cella UITableViewCell * cell = (UITableViewCell *) [[[superstender sender] superview] superview]; // Infer Index Path NSIndexPath * indexPath = [self.tableView indexPathForCell: cell]; // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Accedi a Console NSLog (@ "% @", elemento [@ "titolo"]);
Il problema con questo approccio è che è soggetto a errori. Su iOS 7, questo approccio funziona bene. Su iOS 6, tuttavia, non funziona. Per farlo funzionare su iOS 6, devi implementare il metodo come mostrato di seguito. La gerarchia delle viste di un numero di comuni UIView
sottoclassi, come UITableView
, è cambiato in iOS 7 e il risultato è che l'approccio di cui sopra non produce un risultato coerente.
- (void) didTapButton: (id) sender // Trova la tabella Visualizza la cella UITableViewCell * cell = (UITableViewCell *) [[supender superview] superview]; // Infer Index Path NSIndexPath * indexPath = [self.tableView indexPathForCell: cell]; // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Accedi a Console NSLog (@ "% @", elemento [@ "titolo"]);
Non possiamo controllare se il dispositivo ha iOS 7? Questa è un'ottima idea. Tuttavia, cosa farai quando iOS 8 modifica la gerarchia della vista interna di UITableView
ancora una volta? Hai intenzione di applicare la patch all'applicazione ogni volta che viene introdotta una versione principale di iOS? E che dire di tutti quegli utenti che non aggiornano alla versione più recente (con patch) della tua applicazione? Spero sia chiaro che abbiamo bisogno di una soluzione migliore.
Un approccio migliore consiste nel dedurre il percorso dell'indice della cella nella vista tabella in base alla posizione del file mittente
, il UIButton
esempio, nella vista tabella. Noi usiamo convertPoint: toview:
per realizzare questo. Questo metodo converte il centro del pulsante dal sistema di coordinate del pulsante al sistema di coordinate della vista tabella. Diventa quindi molto facile. Noi chiamiamo indexPathForRowAtPoint:
sul tavolo guarda e passa pointInSuperview
ad esso. Questo ci fornisce un percorso dell'indice che possiamo usare per recuperare l'elemento corretto dall'origine dati.
- (void) didTapButton: (id) sender // Cast Sender in UIButton UIButton * button = (UIButton *) mittente; // Trova punto in Superview CGPoint pointInSuperview = [button.superview convertPoint: button.center toView: self.tableView]; // Infer Index Path NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: pointInSuperview]; // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Accedi a Console NSLog (@ "% @", elemento [@ "titolo"]);
Questo approccio può sembrare complicato, ma in realtà non lo è. È un approccio che non è influenzato dalle modifiche nella gerarchia di visualizzazione di UITableView
e può essere utilizzato in molti scenari, anche nelle viste di raccolta.
C'è un'altra soluzione per risolvere il problema e richiede un po 'più di lavoro. Il risultato, tuttavia, è una visualizzazione del moderno Objective-C. Inizia rivisitando il file di intestazione del file TPSButtonCell
e dichiara un metodo pubblico chiamato setDidTapButtonBlock:
che accetta un blocco.
#importare@interface TPSButtonCell: UITableViewCell @property (weak, nonatomic) IBOutlet UILabel * titleLabel; @property (weak, nonatomic) IBOutlet UIButton * actionButton; - (void) setDidTapButtonBlock: (void (^) (id sender)) didTapButtonBlock; @fine
Nel file di implementazione di TPSButtonCell
creare una proprietà privata denominata didTapButtonBlock
come mostrato di seguito. Si noti che la proprietà attribuita è impostata su copia
, perché i blocchi devono essere copiati per tenere traccia del loro stato acquisito al di fuori dell'ambito originale.
#import "TPSButtonCell.h" @interface TPSButtonCell () @property (copy, nonatomic) void (^ didTapButtonBlock) (id sender); @fine
Invece di aggiungere un obiettivo e un'azione per il UIControlEventTouchUpInside
evento nel controller della vista tableView: cellForRowAtIndexPath:
, aggiungiamo un obiettivo e un'azione in awakeFromNib
nel TPSButtonCell
classe stessa.
- (void) awakeFromNib [super awakeFromNib]; [self.actionButton addTarget: self action: @selector (didTapButton :) forControlEvents: UIControlEventTouchUpInside];
L'implementazione di didTapButton:
è banale.
- (void) didTapButton: (id) sender if (self.didTapButtonBlock) self.didTapButtonBlock (sender);
Questo può sembrare un sacco di lavoro per un semplice pulsante, ma tieni i cavalli fino a quando non li abbiamo rifattorizzati tableView: cellForRowAtIndexPath:
nel TPSViewController
classe. Invece di aggiungere un obiettivo e un'azione al pulsante della cella, impostiamo la cella didTapButtonBlock
. Ottenere un riferimento all'elemento corrispondente dell'origine dati diventa molto, molto facile. Questa soluzione è di gran lunga la soluzione più elegante a questo problema.
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * cell = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath: indexPath]; // Fetch Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Configura cella Visualizza tabella [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", elemento [@ "titolo"], elemento [@ "anno"]]]; [cell setDidTapButtonBlock: ^ (id sender) NSLog (@ "% @", elemento [@ "titolo"]); ]; cella di ritorno;
Anche se il concetto di blocchi esiste da decenni, gli sviluppatori di Cocoa hanno dovuto attendere fino al 2011. I blocchi possono rendere più semplici i problemi complessi e semplificare il codice complesso. Dall'introduzione dei blocchi, Apple ha iniziato a farne un ampio uso nelle proprie API, quindi ti incoraggio a seguire la guida di Apple sfruttando i blocchi nei tuoi progetti.