Nu när vi har en datakälla är det dags att sätta den i arbete. I del 5 av artikelserien visar 99mac hur man använder ett fristående framework och presenterar data i en UITableView.
I förra delen av vår #Kom igång-serie skrev vi en klient som hämtar data från ett publikt API. För att få den att fungera i appen behöver vi länka in vårt framework och berätta för UITableView hur den ska presentera data.
Det första vi behöver göra är att länka in vårt framework i huvudprojektet. Om du har organiserat både huvudprojektet och ditt framework i samma workspace så är det väldigt enkelt.
Markera huvudprojektet i fillistan till vänster
Välj "General"-fliken
Klicka på plustecknet nere under "Linked Frameworks and libraries"
Välj ditt framework i listan
Lägg till ditt framework även under "Embedded binaries" på motsvarande sätt
Om du inte har dem i samma workspace så får du klicka på "Add other…" i steg 4 och 5 och leta reda på ditt framework i filsystemet istället. Utan steg 5 fungerar appen i simulatorn, men inte på en fysisk IOS-enhet.
Nu är det dags att återgå till klasserna vi skapade i del 3, närmare bestämt SearchViewController. Det första vi behöver göra är att importera vårt inlänkade framework. Längst upp i SearchViewController.swift-filen finns redan en import av UIKit. Under den raden lägger vi till "import SLAPI".
Nu behöver vi implementera SLAPIDelegate-protokollet i SearchViewController så att API-klienten kan skicka tillbaka asynkrona sökresultat. Vi behöver även skapa en instansvariabel för API-klienten. Klassdefinitionen för SearchViewController ska nu se ut så här:
class SearchViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate, SLAPIDelegate { var api : SLAPI? = nil // Mer kod.. }
Om du inte gjorde protokollmetoderna optional så klagar #Xcode på att de inte är implementerade. Vi löser det genom att lägga in tomma definitioner av dem i SearchViewController:
func siteLookupComplete(sites: NSArray) { NSLog("Yay!") } func siteLookupFailed(error: NSError) { NSLog("Doh!") } func getDeparturesComplete(departures: NSDictionary) { // Not used } func getDeparturesFailed(error: NSError) { // Not used }
Innan vi kan använda API-klienten måste vi skapa ett SLAPI-objekt, eftersom variabeln sattes till nil ovan. I del 4 skrev vi en init-funktion som tar ett delegate-objekt som argument. Eftersom vi har implementerat delegate-protokollet i den anropande klassen (SearchViewController) så anger vi self som delegate.
SLAPI-objektet skapas med fördel i funktionen viewDidLoad.
override func viewDidLoad() { self.api = SLAPI(delegate: self) // Mer kod.. }
Så, när ska vi anropa searchSite i SLAPI? Det gör vi lämpligtvis när användaren skriver något i sökrutan. I del 3 lade vi in en tom definition av delegate-funktionen updateSearchResultsForSearchController. Den anropas varje gång innehållet i sökfältet ändras, vilket är precis vad vi vill åt.
func updateSearchResultsForSearchController(searchController: UISearchController) { // Ta hand om söksträng self.api?.searchSite(searchController.searchBar.text) }
Här anropas searchSite med den textsträng som har skrivits in i sökfältet. Nu sker all asynkron magi via NSURLSession, och när all data är hämtad anropas vår delegate-funktion siteLookupComplete.
När siteLookupComplete-funktionen anropas får vi en NSArray med sökresultat som argument. I min implementation har jag en hjälpklass som heter SLSite, som i princip bara är en container för data för att bunta ihop det snyggt. Arrayen med sökresultat innehåller alltså SLSite-objekt, som i sin tur innehåller siteID och namn.
Vi behöver en mer bestående referens för sökresultatet, så vi skapar en ny instansvariabel av typen NSArray i SearchViewController.
var dataSource : NSArray = NSArray()
Nu kan vi spara undan sökresultaten i den nya variabeln och beordra den inbäddade UITableView att ladda om sin data. Som vanligt bör UI-uppdateringar ske i huvudtråden, så vi använder funktionen dispatch_async.
func siteLookupComplete(sites: NSArray) { NSLog("Yay!") dataSource = sites dispatch_async(dispatch_get_main_queue(), { self.tableView.reloadData() return }) }
Men, än så länge vet inte vår UITableView var den ska hämta data när vi laddar om den. SearchViewController är dess delegate, men vi måste implementera delegate-funktionerna, närmare bestämt numberOfSectionsInTableView, numberOfRowsInSection och cellForRowAtIndexPath.
numberOfSectionsInTableView anger antalet sektioner i en UITableView. Man kan exempelvis ha en sektion för varje bokstav i en alfabetiskt sorterad lista. Men för vårt syfte här behövs bara en sektion, så den här funktionen blir enkel.
override func numberOfSectionsInTableView(tableView: UITableView) -> Int { // Return the number of sections. return 1 }
numberOfRowsInSection anger hur många rader/objekt som finns i en given sektion. Eftersom vi bara har en sektion så returnerar vi bara antal objekt i datakällan.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // Return the number of rows in the section. if section == 0 { return dataSource.count } return 0 }
I cellForRowAtIndexPath sker den mest relevanta logiken. Denna funktion ska returnera en UITableViewCell för en given så kallad indexPath som anger vilken sektion och vilken rad som efterfrågas.
cellForRowAtIndexPath anropas en gång för varje synlig rad i tabellen. När man skrollar anropas den i takt med att nya rader blir synliga. Av prestandaskäl skapas ett antal UITableViewCells som sedan återanvänds i takt med att de försvinner ur bild. Till detta anges en "Reuse identifier" som definieras för varje sorts cell i Storyboards.
Funktionen dequeueReusableCellWithIdentifier hämtar en ledig cell, eller skapar en ny om inga lediga finns. Därefter kan vi konfigurera cellen genom att sätta dess textLabel till namnet på siten.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("searchTableCell", forIndexPath: indexPath) as! UITableViewCell // Configure the cell... let site : SLSite = dataSource.objectAtIndex(indexPath.row) as! SLSite cell.textLabel?.text = site.getName() return cell }
Så! Nu ska vår lista fyllas med sökresultat om man skriver några tecken. I kommande delar ska jag bland annat visa hur man kan spara favoriter med hjälp av NSUserDefaults.