O celulă de vizualizare de tabelă nu știe despre vizualizarea tabelului din care face parte și este bine. De fapt, așa ar trebui să fie. Cu toate acestea, oamenii care sunt noi în acest concept sunt deseori confundați de acesta. De exemplu, dacă utilizatorul pune un buton într-o celulă de vizualizare a tabelului, cum obțineți calea index a celulei, astfel încât să puteți prelua modelul corespunzător? În acest tutorial, vă voi arăta cum să nu faceți acest lucru, cum se face de obicei, și cum să faceți acest lucru cu stil și eleganță.
Când utilizatorul prăjează o celulă de vizualizare a tabelului, se invocă vizualizarea de tabelă tableView: didSelectRowAtIndexPath:
din UITableViewDelegate
protocol pe delegatul vizualizării mesei. Această metodă acceptă două argumente, vizualizarea tabelului și calea index a celulei care a fost selectată.
Problema pe care o vom aborda în acest tutorial este, totuși, un pic mai complexă. Să presupunem că avem o vedere de masă cu celule, fiecare celulă conținând un buton. Când se atinge butonul, se declanșează o acțiune. În acțiune, trebuie să preluăm modelul care corespunde poziției celulei în vizualizarea tabelului. Cu alte cuvinte, trebuie să cunoaștem calea indexului celulei. Cum putem deduce calea indexului celulei dacă primim doar o referință la butonul care a fost atins? Aceasta este problema pe care o vom rezolva în acest tutorial.
Creați un nou proiect în Xcode selectând Vizualizare individuală șablon din lista de Aplicația iOS template-uri. Denumiți proiectul Blocuri și celule, a stabilit Dispozitive la iPhone, și faceți clic pe Următor →. Spuneți Xcode unde doriți să stocați proiectul și să îl loviți Crea.
Deschide Project Navigator din stânga, selectați proiectul din Proiect și setați Obiectivul de implementare la iOS 6. Facem acest lucru pentru a ne asigura că putem rula aplicația atât pe iOS 6, cât și pe iOS 7. Motivul pentru aceasta va deveni clar mai târziu în acest tutorial.
UITableViewCell
SubclasăSelectați Nou> Fișier ... de la Fişier meniu și alegeți Clasa obiectiv-C din lista de Cocoa Touch template-uri. Denumiți clasa TPSButtonCell
și asigurați-vă că acesta moștenește UITableViewCell
.
Deschideți fișierul de antet al clasei și declarați două puncte de desfacere, a UILabel
instanță numită titleLabel
și a UIButton
instanță numită actionButton
.
#import@interface TPSButtonCell: UITableViewCell @property (slab, nonatomic) IBOutlet UILabel * titleLabel; @property (slab, nonatomic) IBOutlet UIButton * actionButton; @Sfârșit
Deschideți fișierul antet al TPSViewController
clasați și creați o priză denumită tableView
de tip UITableView
. TPSViewController
trebuie, de asemenea, să adopte UITableViewDataSource
și UITableViewDelegate
protocoale.
#import@interface TPSViewController: UIViewController @property (slab, nonatomic) IBOutlet UITableView * tableView; @Sfârșit
De asemenea, trebuie să examinăm scurt fișierul de implementare al controlorului de vizualizare. Deschideți TPSViewController.m și declarați o variabilă statică de tip NSString
pe care o vom folosi ca identificator de refolosire pentru celulele din vizualizarea de tabel.
#import "TPSViewController.h" @implementation TPSViewController static NSString * CellIdentifier = @ "CellIdentifier"; //… // @Sfârșit
Deschideți tabloul de bord principal al proiectului, Main.Storyboard și trageți o vizualizare de tabel la vizualizarea controlerului de vizualizare. Selectați vizualizarea tabelului și conectați-o sursă de date
și delega
ieșiri cu instanța controlerului de vizualizare. Cu ecranul de masă selectat încă, deschideți Atribuții Inspector și setați numărul Prototipul celulelor la 1
. Conţinut atributul ar trebui setat la Prototypes dinamice. Ar trebui să vedeți acum o celulă de prototip în tabelul de vizualizare.
Selectați celula prototip și setați-o Clasă la TPSButtonCell
în Inspectorul de identitate. Cu celula selectată, deschideți Atribuții Inspector și setați Stil atribuit lui Personalizat si Identificator la CellIdentifier.
Trageți a UILabel
exemplu de la Biblioteca de obiecte la vizualizarea conținutului celulei și repetați acest pas pentru a UIButton
instanță. Selectați celula, deschideți Conectarea inspectorului, și conectați titleLabel
și actionButton
cu omologii lor din celula prototip.
Înainte să ne întoarcem în cod, trebuie să facem încă o conexiune. Selectați controlerul de vizualizare, deschideți Conectarea inspectorului încă o dată, și conectați controlerul de vizualizare tableView
ieșiți cu vizualizarea tabelului în tabloul de bord. Asta e pentru interfața cu utilizatorul.
Să populam vizualizarea tabelului cu câteva filme notabile care au fost lansate în 2013. În TPSViewController
clasă, să declare o proprietate de tip NSArray
și numește-o sursă de date
. Variabila de instanță corespunzătoare va deține filmele pe care le vom afișa în vizualizarea tabelului. Popula sursă de date
cu o duzină de filme în controlerul de vizualizare viewDidLoad
metodă.
#import "TPSViewController.h" @interface TPSViewController () @property (puternic, nonatomic) NSArray * dataSource; @Sfârșit
- (vid) viewDidLoad [super viewDidLoad]; // Sursa de date de instalare self.dataSource = @ [@ @ "title": @ "Gravity", @ "year": @ (@ 2013) "anul": @ (2013), @ @ "titlu": @ "înainte de miezul nopții", @ "anul": @ ": @ @ @ @ @ @ @ @ @ @ @ @ @: @ @ @ @ @ (2013), @ @ "titlu": @ "Nebraska", @ "anul": @ (2013) @ @ , @ @ @ @ @ @ @ @ @ @ @ @ @: @ @ @ @ "title": @ "The Conjuring", @ "an": @ (@ 2013) @ ("2013"), @ @ "title": @ "The Attack", @ titlul ": @" Noi suntem ceea ce suntem ", @" anul ": @ (2013), @ @" titlu ": @" Ceva în aer ", @" anul ": @ (2013);
UITableViewDataSource
ProtocolPunerea în aplicare a directivei UITableViewDataSource
protocolul este foarte ușor. Trebuie doar să implementăm numberOfSectionsInTableView:
, tableView: numberOfRowsInSection:
, și tableView: cellForRowAtIndexPath:
.
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView întoarcere auto.dataSursă? 1: 0; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) sectiunea return self.dataSource? auto.dataSource.count: 0; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * celula = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier pentruIndexPath: indexPath]; // Returnați elementul NSDictionary * item = [auto.dataSource objectAtIndex: indexPath.row]; // Configurează tabela de vizualizare a celulei [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", element [@ "title"]; [cell.actionButton addTarget: acțiune auto: @selector (didTapButton :) forControlEvents: UIControlEventTouchUpInside]; celule retur;
În tableView: cellForRowAtIndexPath:
, folosim același identificator pe care l-am stabilit în tabloul de bord principal, CellIdentifier
, pe care am declarat-o mai devreme în tutorial. Am aruncat celula într-o instanță TPSButtonCell
, preluați elementul corespunzător din sursa de date și actualizați eticheta de titlu a celulei. De asemenea, adăugăm o țintă și o acțiune pentru UIControlEventTouchUpInside
evenimentul butonului.
Nu uitați să adăugați o declarație de import pentru TPSButtonCell
clasă în partea de sus a TPSViewController.m.
#import "TPSButtonCell.h"
Pentru a împiedica aplicația să cedeze la atingerea unui buton, implementați-o didTapButton:
așa cum se arată mai jos.
- (void) didTapButton: (id) expeditor NSLog (@ "% s", __PRETTY_FUNCTION__);
Construiți proiectul și rulați-l în Simulatorul iOS pentru a vedea ce avem până acum. Ar trebui să vedeți o listă de filme și să atingeți butonul din partea dreaptă să înregistreze un mesaj către consola Xcode. Grozav. E timpul pentru carnea tutorialului.
Când utilizatorul pune butonul în dreapta, va trimite un mesaj de didTapButton:
la controlerul de vizualizare. Aproape întotdeauna trebuie să cunoașteți calea index a celulei de vizualizare a tabelului în care este introdus butonul. Dar cum obțineți calea index? După cum am menționat, există trei abordări pe care le puteți lua. Să ne uităm mai întâi cum să nu o facem.
Aruncați o privire la punerea în aplicare a didTapButton:
și încercați să aflați ce este în neregulă cu ea. Observați pericolul? Lasa-ma sa te ajut. Rulați aplicația mai întâi pe iOS 7 și apoi pe iOS 6. Uitați-vă la ce ieșiri Xcode la consola.
- (void) didTapButton: (id) expeditor // a se vedea tabelul de vizualizare a celulei UITableViewCell * cell = (UITableViewCell *) [[[expeditor supraveghere] supervize] superview]; // Cale Index Inferior NSIndexPath * indexPath = [auto.tableView indexPathForCell: celula]; // Returnați elementul NSDictionary * item = [auto.dataSource objectAtIndex: indexPath.row]; // Log in Consola NSLog (@ "% @", element [@ "title"]);
Problema cu această abordare este că este predispusă la erori. În iOS 7, această abordare funcționează foarte bine. Pe iOS 6, cu toate acestea, nu funcționează. Pentru a funcționa în iOS 6, va trebui să implementați metoda de mai jos. Ierarhia de vizualizare a unui număr de commons UIView
subclase, cum ar fi UITableView
, sa schimbat în iOS 7 și rezultatul este că abordarea de mai sus nu produce un rezultat consecvent.
- (void) didTapButton: (id) expeditor // Găsiți tabelul View Cell UITableViewCell * cell = (UITableViewCell *) [supervizor expeditor] superview]; // Cale Index Inferior NSIndexPath * indexPath = [auto.tableView indexPathForCell: celula]; // Returnați elementul NSDictionary * item = [auto.dataSource objectAtIndex: indexPath.row]; // Log in Consola NSLog (@ "% @", element [@ "title"]);
Nu putem verifica dacă dispozitivul rulează iOS 7? Aceasta este o idee foarte bună. Cu toate acestea, ce veți face atunci când iOS 8 modifică ierarhia internă de vizualizare din UITableView
încă o dată? Vrei să îți completezi aplicația de fiecare dată când este lansată o versiune majoră de iOS? Și cum rămâne cu toți acei utilizatori care nu fac upgrade la ultima versiune (patchată) a aplicației dvs.? Sper că este clar că avem nevoie de o soluție mai bună.
O abordare mai bună este de a deduce calea indexului celulei în vizualizarea tabelului pe baza poziției expeditor
, UIButton
exemplu, în vizualizarea tabelului. Folosim convertPoint: toview:
pentru a realiza acest lucru. Această metodă convertește centrul butonului de la sistemul de coordonate al butonului la sistemul de coordonate din tabel. Apoi devine foarte ușor. Noi sunam indexPathForRowAtPoint:
pe ecranul de masă și treci pointInSuperview
la el. Acest lucru ne oferă o cale de index pe care o putem folosi pentru a prelua elementul corect din sursa de date.
- (void) didTapButton: (id) expeditor // Cast Sender la UIButton UIButton * button = (UIButton *) expeditor; // Găsiți Punct în Superview CGPoint pointInSuperview = [button.superview convertPoint: button.center toView: self.tableView]; // Cale index inferior NSIndexPath * indexPath = [auto.tableView indexPathForRowAtPoint: pointInSuperview]; // Returnați elementul NSDictionary * item = [auto.dataSource objectAtIndex: indexPath.row]; // Log in Consola NSLog (@ "% @", element [@ "title"]);
Această abordare poate părea greoaie, dar de fapt nu este. Este o abordare care nu este afectată de modificările din ierarhia de vizualizare din UITableView
și poate fi utilizat în multe scenarii, inclusiv în vederea colecțiilor.
Există încă o soluție pentru a rezolva problema și necesită un pic mai mult de lucru. Rezultatul, totuși, este o prezentare a Obiectivului-C modern. Începeți prin a revizui fișierul antet al TPSButtonCell
și să declare o metodă publică numită setDidTapButtonBlock:
care acceptă un bloc.
#import@interface TPSButtonCell: UITableViewCell @property (slab, nonatomic) IBOutlet UILabel * titleLabel; @property (slab, nonatomic) IBOutlet UIButton * actionButton; - (void) setDidTapButtonBlock: (void (^) (expeditor id)) didTapButtonBlock; @Sfârșit
În fișierul de implementare din TPSButtonCell
creați o proprietate privată numită didTapButtonBlock
așa cum se arată mai jos. Rețineți că proprietatea atribuită este setată la copie
, deoarece blocurile trebuie copiate pentru a urmări starea lor captată în afara domeniului original.
#import "TPSButtonCell.h" @interface TPSButtonCell () @property (copie, nonatomic) void (^ didTapButtonBlock) (expeditor id); @Sfârșit
În loc să adăugați o țintă și o acțiune pentru UIControlEventTouchUpInside
eveniment în controlerul de vizualizare tableView: cellForRowAtIndexPath:
, adăugăm o țintă și o acțiune în awakeFromNib
în TPSButtonCell
clasa însăși.
- (void) awakeFromNib [super awakeFromNib]; [self.actionButton addTarget: acțiune auto: @selector (didTapButton :) forControlEvents: UIControlEventTouchUpInside];
Implementarea sistemului didTapButton:
este banal.
- (void) didTapButton: (id) expeditor if (self.didTapButtonBlock) auto.didTapButtonBlock (expeditor);
Acest lucru poate părea ca o mulțime de muncă pentru un buton simplu, dar vă țineți cai până când am refactored tableView: cellForRowAtIndexPath:
în TPSViewController
clasă. În loc să adăugăm o țintă și o acțiune la butonul celulei, am setat celula celulei didTapButtonBlock
. Obținerea unei trimiteri la elementul corespunzător al sursei de date devine foarte, foarte ușor. Această soluție este de departe cea mai elegantă soluție la această problemă.
- (UITableViewCell *) tabelView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * celula = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier pentruIndexPath: indexPath]; // Returnați elementul NSDictionary * item = [auto.dataSource objectAtIndex: indexPath.row]; // Configurează tabela de vizualizare a celulei [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", element [@ "title"]; [cell setDidTapButtonBlock: ^ (expeditor id) NSLog (@ "% @", element [@ "title"]); ]; celule retur;
Chiar dacă conceptul de blocuri a fost în jur de zeci de ani, dezvoltatorii de cacao au fost nevoiți să aștepte până în 2011. Blocurile pot face probleme complexe mai ușor de rezolvat și fac codul mai simplu. De la introducerea blocurilor, Apple a început să le folosească pe scară largă în propriile lor API-uri, așa că vă încurajez să urmați plumbul Apple, profitând de blocuri în proiectele proprii.