Blochează și vizualizează celulele pe iOS


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ță.

1. Introducere

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.

2. Configurarea proiectului

Pasul 1: Creați un proiect

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.

Pasul 2: Actualizați obiectivul de implementare

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.

Pasul 3: Creați 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

Pasul 4: Actualizați controlerul de vizualizare

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

Pasul 5: Interfața utilizatorului

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.

3. Populația tabelului

Pasul 1: Creați o sursă de date

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

Pasul 2: Implementați UITableViewDataSource Protocol

Punerea î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.

4. Cum să nu o faceți

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ă.

5. 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.

6. Soluția elegantă

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; 

Concluzie

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.

Cod