Een tabelweergavecel weet niet van het tabeloverzicht waartoe het behoort en dat is prima. In feite is dat hoe het zou moeten zijn. Mensen die nieuw zijn in dit concept, worden er echter vaak door verward. Als de gebruiker bijvoorbeeld op een knop in een tabelweergavecel tikt, hoe verkrijgt u dan het indexpad van de cel zodat u het bijbehorende model kunt ophalen? In deze zelfstudie laat ik je zien hoe je dit niet moet doen, hoe het meestal wordt gedaan en hoe je dit moet doen met stijl en elegantie.
Wanneer de gebruiker op een tabelweergavecel tikt, wordt de tabelweergave opgeroepen tableView: didSelectRowAtIndexPath:
van de UITableViewDelegate
protocol op de tabelweergave gedelegeerde. Deze methode accepteert twee argumenten, de tabelweergave en het indexpad van de cel die is geselecteerd.
Het probleem dat we in deze zelfstudie zullen aanpakken, is echter een beetje ingewikkelder. Stel dat we een tabelweergave hebben met cellen, waarbij elke cel een knop bevat. Wanneer op de knop wordt getikt, wordt een actie geactiveerd. In de actie moeten we het model ophalen dat overeenkomt met de positie van de cel in de tabelweergave. Met andere woorden, we moeten het indexpad van de cel kennen. Hoe leiden we het indexpad van de cel af als we alleen een verwijzing krijgen naar de knop waarop werd getikt? Dat is het probleem dat we in deze tutorial zullen oplossen.
Maak een nieuw project in Xcode door de. Te selecteren Toepassing enkele weergave sjabloon uit de lijst van iOS-applicatie sjablonen. Geef het project een naam Blokken en cellen, reeks apparaten naar iPhone, en klik volgende. Vertel Xcode waar je het project wilt opslaan en klik creëren.
Open de Project Navigator aan de linkerkant, selecteer het project in de project sectie en stel de Deployment Target naar iOS 6. We doen dit om ervoor te zorgen dat we de applicatie zowel op iOS 6 als iOS 7 kunnen uitvoeren. De reden hiervoor zal later in deze tutorial duidelijk worden.
UITableViewCell
subklassekiezen Nieuw> Bestand ... van de het dossier menu en kies Objectieve C-klasse uit de lijst van Cocoa Touch sjablonen. Geef de klas een naam TPSButtonCell
en zorg ervoor dat het erft van UITableViewCell
.
Open het header-bestand van de class en declareer twee outlets, a UILabel
exemplaar genoemd titleLabel
en een UIButton
exemplaar genoemd actionButton
.
#importeren@interface TPSButtonCell: UITableViewCell @property (weak, nonatomic) IBOutlet UILabel * titleLabel; @property (weak, nonatomic) IBOutlet UIButton * actionButton; @einde
Open het header-bestand van de TPSViewController
klasse en maak een outlet genaamd tableView
van type UITableView
. De TPSViewController
moet ook de UITableViewDataSource
en UITableViewDelegate
protocollen.
#importeren@interface TPSViewController: UIViewController @property (weak, nonatomic) IBOutlet UITableView * tableView; @einde
We moeten ook een korte blik werpen op het implementatiebestand van de view controller. Open TPSViewController.m en declareer een statische variabele van het type NSString
die we zullen gebruiken als de hergebruik-ID voor de cellen in de tabelweergave.
#import "TPSViewController.h" @implementation TPSViewController static NSString * CellIdentifier = @ "CellIdentifier"; // ... // @end
Open het hoofdverhaallboard van het project, Main.Storyboard, en sleep een tabelweergave naar de weergave van de view controller. Selecteer de tabelweergave en sluit deze aan databron
en delegeren
outlets met de view controller-instantie. Met de tabelweergave nog steeds geselecteerd, opent u de Kenmerken Inspector en stel het aantal in Prototype cellen naar 1
. De Inhoud attribuut moet worden ingesteld op Dynamische prototypen. U zou nu één prototype-cel moeten zien in de tabelweergave.
Selecteer de prototype-cel en stel deze in Klasse naar TPSButtonCell
in de Identiteitsinspecteur. Terwijl de cel nog steeds is geselecteerd, opent u de Kenmerken Inspector en stel de Stijl attribuut aan gewoonte en de Identifier naar CellIdentifier.
Sleep een UILabel
bijvoorbeeld van de Objectbibliotheek naar de contentweergave van de cel en herhaal deze stap voor a UIButton
aanleg. Selecteer de cel, open de Verbindingen Inspecteur, en verbind de titleLabel
en actionButton
verkooppunten met hun tegenhangers in de prototype cel.
Voordat we teruggaan naar de code, moeten we nog een verbinding maken. Selecteer de view controller, open de Verbindingen Inspecteur nog een keer en sluit de view controller's aan tableView
outlet met de tafelweergave op het storyboard. Dat is het voor de gebruikersinterface.
Laten we de tabelweergave vullen met enkele opmerkelijke films die in 2013 zijn uitgebracht TPSViewController
klasse, verklaren een eigenschap van het type NSArray
en noem het databron
. De bijbehorende instantievariabele bevat de films die we in de tabelweergave zullen weergeven. Bevolken databron
met een tiental films in de view controller's viewDidLoad
methode.
#import "TPSViewController.h" @interface TPSViewController () @property (strong, nonatomic) NSArray * dataSource; @einde
- (void) viewDidLoad [super viewDidLoad]; // Setup Data Source self.dataSource = @ [@ @ "title": @ "Gravity", @ "year": @ (2013), @ @ "title": @ "12 Years a Slave", @ "jaar": @ (2013), @ @ "titel": @ "Voor middernacht", @ "jaar": @ (2013), @ @ "titel": @ "American Hustle", @ "jaar ": @ (2013), @ @" titel ": @" Blackfish ", @" jaar ": @ (2013), @ @" titel ": @" Captain Phillips ", @" jaar ": @ (2013), @ @ "titel": @ "Nebraska", @ "jaar": @ (2013), @ @ "titel": @ "Rush", @ "jaar": @ (2013) , @ @ "titel": @ "Frozen", @ "jaar": @ (2013), @ @ "titel": @ "Star Trek Into Darkness", @ "jaar": @ (2013), @ @ "title": @ "The Conjuring", @ "year": @ (2013), @ @ "title": @ "Side Effects", @ "year": @ (2013), @ @ "title": @ "The Attack", @ "year": @ (2013), @ @ "title": @ "The Hobbit", @ "year": @ (2013), @ @ " title ": @" We Are What We Are ", @" year ": @ (2013), @ @" title ": @" Something in the Air ", @" year ": @ (2013)];
UITableViewDataSource
ProtocolDe implementatie van de UITableViewDataSource
protocol is heel eenvoudig. We hoeven het alleen maar te implementeren numberOfSectionsInTableView:
, tableView: numberOfRowsInSection:
, en tableView: cellForRowAtIndexPath:
.
- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView return self.dataSource? 1: 0; - (NSInteger) tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) sectie return self.dataSource? self.dataSource.count: 0; - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * cell = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath: indexPath]; // Ophalen Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Tabel tabelweergave configureren [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", item [@ "title"], item [@ "year"]]]; [cel.actionButton addTarget: self action: @selector (didTapButton :) forControlEvents: UIControlEventTouchUpInside]; terugkeer cel;
In tableView: cellForRowAtIndexPath:
, we gebruiken dezelfde ID die we in het hoofd storyboard hebben ingesteld, CellIdentifier
, die we eerder in de tutorial hebben verklaard. We gooien de cel naar een instantie van TPSButtonCell
, haal het overeenkomstige item uit de gegevensbron en werk het titellabel van de cel bij. We voegen ook een doel en actie toe voor de UIControlEventTouchUpInside
gebeurtenis van de knop.
Vergeet niet om een importinstructie toe te voegen voor de TPSButtonCell
klasse bovenaan TPSViewController.m.
#import "TPSButtonCell.h"
Om te voorkomen dat de applicatie crasht wanneer er op een knop wordt getikt, implementeer didTapButton:
zoals hieronder getoond.
- (void) didTapButton: (id) afzender NSLog (@ "% s", __PRETTY_FUNCTION__);
Bouw het project en voer het uit in de iOS Simulator om te zien wat we tot nu toe hebben gekregen. Je zou een lijst met films moeten zien en op de knop in de rechterlogboeken een bericht naar de Xcode-console moeten tikken. Super goed. Het is tijd voor het eten van de tutorial.
Wanneer de gebruiker op de knop aan de rechterkant tikt, wordt er een bericht van verzonden didTapButton:
naar de view controller. U moet bijna altijd het indexpad weten van de tabelweergavecel waarin de knop zich bevindt. Maar hoe krijgt u dat indexpad? Zoals ik al zei, zijn er drie benaderingen die u kunt nemen. Laten we eerst kijken hoe het niet moet.
Bekijk de implementatie van didTapButton:
en probeer erachter te komen wat er mis mee is. Zie je het gevaar? Laat me je helpen. Start de applicatie eerst op iOS 7 en vervolgens op iOS 6. Bekijk wat Xcode uitvoert naar de console.
- (void) didTapButton: (id) afzender // Find Table View Cell UITableViewCell * cell = (UITableViewCell *) [[supervisor afzender superview] superview]; // Infer Index Path NSIndexPath * indexPath = [self.tableBekijk indexPathForCell: cel]; // Ophalen Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Log naar Console NSLog (@ "% @", item [@ "title"]);
Het probleem met deze aanpak is dat het foutgevoelig is. Op iOS 7 werkt deze aanpak prima. Op iOS 6 werkt het echter niet. Om het op iOS 6 te laten werken, zou je de methode moeten implementeren zoals hieronder getoond. De weergavehiërarchie van een aantal commons UIView
subklassen, zoals UITableView
, is veranderd in iOS 7 en het resultaat is dat de bovenstaande benadering geen consistent resultaat oplevert.
- (void) didTapButton: (id) afzender // Find Table View Cell UITableViewCell * cell = (UITableViewCell *) [supervisor afzender supervisie]; // Infer Index Path NSIndexPath * indexPath = [self.tableBekijk indexPathForCell: cel]; // Ophalen Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Log naar Console NSLog (@ "% @", item [@ "title"]);
Kunnen we niet gewoon controleren of het apparaat iOS 7 gebruikt? Dat is een heel goed idee. Wat gaat u echter doen als iOS 8 de interne view-hiërarchie van wijzigt UITableView
nogmaals? Gaat u uw toepassing patchen elke keer dat een belangrijke versie van iOS wordt geïntroduceerd? En hoe zit het met al die gebruikers die niet upgraden naar de nieuwste (gepatchte) versie van uw applicatie? Ik hoop dat het duidelijk is dat we een betere oplossing nodig hebben.
Een betere benadering is om het indexpad van de cel in het tabeloverzicht af te leiden op basis van de positie van de afzender
, de UIButton
bijvoorbeeld in de tabelweergave. We gebruiken convertPoint: toView:
om dit te bereiken. Deze methode converteert het midden van de knop van het coördinatensysteem van de knop naar het coördinatensysteem van de tabelweergave. Het wordt dan heel gemakkelijk. Wij bellen indexPathForRowAtPoint:
op de tafel bekijken en passen pointInSuperview
ernaar toe. Dit geeft ons een indexpad dat we kunnen gebruiken om het juiste item uit de gegevensbron op te halen.
- (void) didTapButton: (id) afzender // Afzender casten naar UIButton UIButton * button = (UIButton *) afzender; // Zoek punt in Superview CGPoint pointInSuperview = [button.superview convertPoint: button.center toView: self.tableView]; // Infer Index Path NSIndexPath * indexPath = [self.tableBekijk indexPathForRowAtPoint: pointInSuperview]; // Ophalen Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Log naar Console NSLog (@ "% @", item [@ "title"]);
Deze aanpak lijkt misschien omslachtig, maar is dat in feite niet. Het is een benadering die niet wordt beïnvloed door wijzigingen in de weergavehiërarchie van UITableView
en het kan in veel scenario's worden gebruikt, inclusief in verzamelaanzichten.
Er is nog een oplossing om het probleem op te lossen en het vereist wat meer werk. Het resultaat is echter een weergave van moderne Objective-C. Begin met het reviseren van het header-bestand van de TPSButtonCell
en verklaar een openbare methode genaamd setDidTapButtonBlock:
dat accepteert een blok.
#importeren@interface TPSButtonCell: UITableViewCell @property (weak, nonatomic) IBOutlet UILabel * titleLabel; @property (weak, nonatomic) IBOutlet UIButton * actionButton; - (void) setDidTapButtonBlock: (void (^) (id afzender)) didTapButtonBlock; @einde
In het implementatiebestand van TPSButtonCell
maak een privé-eigenschap met de naam didTapButtonBlock
zoals hieronder getoond. Merk op dat de toegewezen eigenschap is ingesteld op kopiëren
, omdat blokken moeten worden gekopieerd om hun vastgelegde status bij te houden buiten het oorspronkelijke bereik.
#import "TPSButtonCell.h" @interface TPSButtonCell () @property (copy, nonatomic) void (^ didTapButtonBlock) (id afzender); @einde
In plaats van een doel en actie toe te voegen voor de UIControlEventTouchUpInside
gebeurtenis in de weergavecontroller tableView: cellForRowAtIndexPath:
, we voegen een doel en actie toe in awakeFromNib
in de TPSButtonCell
klasse zelf.
- (void) awakeFromNib [super awakeFromNib]; [self.actionButton addTarget: self action: @selector (didTapButton :) forControlEvents: UIControlEventTouchUpInside];
De implementatie van didTapButton:
is triviaal.
- (void) didTapButton: (id) afzender if (self.didTapButtonBlock) self.didTapButtonBlock (afzender);
Dit lijkt misschien heel veel werk voor een eenvoudige knop, maar houd je paarden vast totdat we hebben gerefactored tableView: cellForRowAtIndexPath:
in de TPSViewController
klasse. In plaats van een doel en actie toe te voegen aan de knop van de cel, stellen we de cellen in didTapButtonBlock
. Het krijgen van een verwijzing naar het overeenkomstige item van de gegevensbron wordt heel, heel eenvoudig. Deze oplossing is veruit de elegantste oplossing voor dit probleem.
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath TPSButtonCell * cell = (TPSButtonCell *) [tableView dequeueReusableCellWithIdentifier: CellIdentifier forIndexPath: indexPath]; // Ophalen Item NSDictionary * item = [self.dataSource objectAtIndex: indexPath.row]; // Tabel tabelweergave configureren [cell.titleLabel setText: [NSString stringWithFormat: @ "% @ (% @)", item [@ "title"], item [@ "year"]]]; [cell setDidTapButtonBlock: ^ (id afzender) NSLog (@ "% @", item [@ "title"]); ]; terugkeer cel;
Hoewel het concept van blokken al tientallen jaren bestaat, moesten Cocoa-ontwikkelaars wachten tot 2011. Blokken kunnen complexe problemen eenvoudiger oplossen en maken complexe code eenvoudiger. Sinds de introductie van blokken is Apple begonnen met het gebruik ervan in hun eigen API's, dus ik moedig je aan om Apple's voorbeeld te volgen door gebruik te maken van blokken in je eigen projecten.