GUIDE

Kom igång med IOS-utveckling, del 6

Ett tillägg kan vara ett bra sätt att ge mervärde till sin app. I del 6 av artikelserien visar 99mac hur man skapar ett Idag-tillägg och delar data med huvudappen.

Ett Idag-tillägg visas som en widget i notiscentret i Ios, och kan vara ett fint komplement till en app. I mitt fall kommer tillägget att vara huvudsyftet med appen, men jag behöver ändå huvudappen för att välja hållplatser och inställningar.

Mindre mängder data för inställningar sparas med fördel med hjälp av NSUserDefaults som är väldigt enkelt att använda. Men för att få tillgång till samma data i ett tillägg krävs lite handpåläggning, eftersom IOS betraktar ett tillägg som en helt separat enhet, eller Sandbox.

I den här artikeln visar jag hur du enkelt skapar ett tillägg och delar data mellan den och huvudappen.

Skapa tillägg

Ett tillägg ingår till skillnad från ett framework i samma projekt som huvudappen, men det blir ett eget target, och därmed en separat körbar app.

Med projektet markerat i fillistan till vänster, välj File -> New -> Target -> Today Extension. Detta skapar en ny mapp med en TodayViewController och en storyboard. Om du nu väljer att köra Today-appen i simulatorn så ser du en fin "Hello World"-etikett som motsvarar ditt nya tillägg.

Ett Idag-tillägg fungerar alltså precis som en vanlig app, där du skapar UI i en storyboard och lägger logiken i en ViewController. Dock med skillnaden att tillägget saknar app delegates och dylikt, eftersom de är inbäddade i en systemapp, och du behöver bara hantera en enda ViewController.

Hantera UI-uppdateringar

Det finns en väsentlig skillnad i hur ett Idag-tillägg hanterar UI-uppdateringar, jämfört med vanliga appar. I TodayViewController.swift finns en fördefinierad metod som kallas widgetPerformUpdateWithCompletionHandler.

Denna metod anropas bland annat när Idag-vyn öppnas, och som argument skickas en completion handler med. Här är det lämpligt att köra API-anrop eller annat för att hämta färsk data. Completion handlern ska du köra med ett argument som anger om det finns uppdateringar att visa eller ej.

Det är viktigt att verkligen köra completion handlern varje gång widgetPerformUpdateWithCompletionHandler anropas. Annars kan operativsystemet till slut strunta i att fråga om uppdateringar, vilket kan leda till att ditt tillägg inte fungerar korrekt.

Här är ett exempel på hur jag kan hantera completion handlern i mitt Idag-tillägg:

class TodayViewController: UIViewController, NCWidgetProviding, UITableViewDataSource, UITableViewDelegate, SLAPIDelegate {
    
    var objects = NSArray()

    // Instansvariabel för att spara completion handlern:
    var compHandler : ((NCUpdateResult) -> Void)? = nil
    
    override func viewDidDisappear(animated: Bool) {
        if let ch = self.compHandler {
            ch(NCUpdateResult.Failed) // Anropa completion handlern med Failed om vyn försvinner innan resultatet har kommit
        }
    }
    
    func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
        compHandler = completionHandler // Spara undan handlern för senare bruk
        let api = SLAPI(delegate: self)
        api.getDepartures(<ett ID hämtat från exempelvis NSUserDefaults>)
        // completion handlern anropas inte nu, utan i delegate-metoderna för det asynkrona anropet
    }

    func getDeparturesComplete(departures: NSDictionary) {
        objects = departures.objectForKey("departures") as! NSArray

        // Uppdatera UI i huvudtråden
        dispatch_async(dispatch_get_main_queue(), {
            self.tableView.reloadData()
            return
        })
        if let ch = self.compHandler {
            ch(NCUpdateResult.NewData) // Anropa completion handlern och berätta att det finns en uppdatering
            self.compHandler = nil // Vi behöver inte anropa den flera gånger, så ta bort den
        }
    }
    
    func getDeparturesFailed(error: NSError) {
        if let ch = self.compHandler {
            ch(NCUpdateResult.Failed) // Anropa completion handlern och berätta att hämtandet misslyckades
            self.compHandler = nil
        }
    }

    // … Mer kod för att hantera table view etc…

}

Eftersom jag hämtar data asynkront behöver jag spara undan completion handlern i en instansvariabel när widgetPerformUpdateWithCompletionHandler anropas, så att jag kan köra den när jag får ett resultat.

När resultatet har kommit (i delegate-metoden getDeparturesComplete) kör jag completion handlern med argumentet NCUpdateResult.NewData som berättar att jag har uppdaterat innehållet med färsk data.

Om API-anropet misslyckas (delegate-metoden getDeparturesFailed) eller om vyn försvinner innan resultatet har kommit (viewDidDisappear) så kör jag completion handlern med argumentet NCUpdateResult.Failed.

Dela data

Det enklaste sättet att spara bestående data är via NSUserDefaults. I standardutförandet fungerar det dock bara inom enskilda appar, inte mellan app och tillägg. NSUserDefaults lagrar data med en enkel key-value store.

func storeString() {
    let defaults = NSUserDefaults.standardUserDefaults()
    defaults.setObject("99mac.se", forKey: "site")
    defaults.objectForKey("site") // Endast åtkomlig inom samma app
}

För att komma runt det behöver man skapa en så kallad App Group som både huvudappen och tillägget ingår i. Därefter lagrar man NSUserDefaults för den gruppen.

För att skapa en App Group krävs först och främst att du är betalande utvecklare.

  1. Markera ditt projekt i listan till vänster och välj det Target som motsvarar huvudappen

  2. Välj "Capabilities"-fliken

  3. Aktivera "App Groups" en bit ner i listan

  4. Välj din utvecklarprofil i drop down-listan

  5. Om det uppstår några anmärkningar, klicka på "Fix issues" för att åtgärda dem.

  6. Klicka på plustecknet under den tomma listan över App Groups

  7. Välj ett unikt gruppnamn. Lämpligt namn är exempelvis group.<bundle-ID>

Nu har du skapat en App Group, men det är bara huvudappen som ingår i den. Upprepa steg 1-3 för det Target som motsvarar ditt tillägg, och bocka därefter i din nyskapade App Group för att inkludera även tillägget i gruppen.

För att dina data ska bli åtkomliga för alla appar i en App Group behöver du skapa en instans av NSUserDefaults som är specifik för gruppen.

func storeStringForAppGroup() {
    let defaults = NSUserDefaults(suiteName: "<ditt unika gruppnamn>")
    defaults?.setObject("99mac.se", forKey: "site") 
    defaults?.objectForKey("site") // Åtkomlig för alla appar och tillägg i ovan nämnda grupp
}

Dela kod

Om du vill dela kod mellan huvudappen och tillägget, exempelvis API-klienten i mitt exempel, måste du länka in det externa frameworket även i tillägget. Det görs på samma sätt som jag beskrev i del 5, fast du väljer det Target som motsvarar tillägget istället för huvudappen.

Resten fungerar också på precis samma sätt. Importera biblioteket i början av .swift-filen och använd klasser och metoder rakt av.

Länkar

I avsnitt 27 radar Fulkultur-panelen upp en hel hög med bra tips att läsa, se på och spela under julens ledigheter.

Nu på lördag den 10:e december anordnar Geeks Gaming en spelkväll med Nintendo-tema som livesänds från Geeks lokaler i Stockholm.

I år har vår syskonsajt FZ varit ett fyrtorn för spelbevakning och glädje i hela 20 år, och detta firas med dunder och brak i kväll.

Google satsar stort på vindkraft, och meddelar att företaget kommer att använda hundra procent förnyelsebar energi från och med nästa år.

Med den sociala appen Miitomo kan du dela din julglädje med andra Nintendo-fantaster, och dessutom samla på dig rabatter på digitala Nintendo-köp.

Julglädjen fortsätter i Geeks Julkalender och bakom den sjunde luckan döljs ett paketerbjudande på en stol från Playseat för dig som tar simulatorspelen på största allvar.