En tutorial om iOS 8 App Extensions

Få havde prøvet det før (se her), men det var Apple, der med den første iPhone definerede, hvordan en smartphone og et mobilt operativsystem skulle se ud. Apple lavede et utroligt gennembrud inden for hardware og brugeroplevelse. Vi glemmer dog ofte, at de også satte standarder for, hvordan et mobilt operativsystem skal fungere, og hvordan applikationer til smartphones skal laves.

Bygge betonmure mellem applikationerne, så de blev fuldstændig isolerede og uvidende om hinanden, var den bedste metode til at holde dem sikre og beskytte deres data. Alle aktiviteter blev nøje overvåget af iOS, og der var kun en håndfuld handlinger, som en app kunne have foretaget uden for dens område.

“Afholdenhed er den bedste beskyttelse!” – men hvor er det sjove i det?

Det tog dem et stykke tid; for lang tid, hvis du spørger mig, men med iOS 8 besluttede Apple sig for at have det lidt sjovt. iOS 8 introducerede et nyt koncept kaldet App Extensions. Denne nye funktion nedbrød ikke væggene mellem programmerne, men den åbnede nogle få døre, der gav blid, men håndgribelig kontakt mellem nogle apps. Den seneste opdatering gav iOS-udviklere en mulighed for at tilpasse iOS-økosystemet, og vi er spændte på, at denne vej også åbnes.

Hvad er iOS 8 App Extensions, og hvordan fungerer de?

Simpelt sagt giver iOS 8 App Extensions en ny metode til at interagere med dit program, uden at du starter det eller viser det på skærmen.

Som forventet har Apple sørget for at holde styr på alt, så der er blot en håndfuld nye indgangspunkter, som din applikation kan tilbyde:

  • I dag (også kaldet en widget) – en udvidelse, der vises i Visningen i dag i Meddelelsescenter, viser korte oplysninger og giver mulighed for udførelse af hurtige opgaver.
  • Deling – en udvidelse, der gør det muligt for din app at dele indhold med brugere på sociale netværk og andre delingstjenester.
  • Handling – en udvidelse, der gør det muligt at oprette brugerdefinerede handlingsknapper i handlingsarket, så brugerne kan få vist eller omdanne indhold, der stammer fra en værtsapp.
  • Fotoredigering – en udvidelse, der giver brugerne mulighed for at redigere et foto eller en video i appen Fotos.
  • Dokumentudbyder – en udvidelse, der bruges til at give andre apps adgang til de dokumenter, der administreres af din app.
  • Brugerdefineret tastatur – en udvidelse, der erstatter systemtastaturet.

App-udvidelser er ikke selvstændige apps. De giver udvidet funktionalitet i appen (som kan tilgås fra andre apps, kaldet værtsapps), som er beregnet til at være effektiv og fokuseret på en enkelt opgave. De har deres egen binære fil, egen kodesignatur og eget sæt af elementer, men leveres via App Store som en del af den indeholdende apps binære fil. En (indeholdende) app kan have mere end én udvidelse. Når brugeren installerer en app, der har udvidelser, vil de være tilgængelige i hele iOS.

Lad os se på et eksempel: En bruger finder et billede ved hjælp af Safari, trykker på deleknappen og vælger din applikations udvidelse til deling. Safari “taler” med iOS Social framework, som indlæser og præsenterer udvidelsen. Udvidelsens kode kører, overfører data ved hjælp af systemets instantierede kommunikationskanaler, og når opgaven er udført – Safari river udvidelsesvisningen ned. Kort tid efter afslutter systemet processen, og din applikation blev aldrig vist på skærmen. Alligevel gennemførte den en billeddelingsfunktion.

iOS er ved hjælp af interprocesskommunikation ansvarlig for at sikre, at værtsappen og en app-udvidelse kan arbejde sammen. Udviklerne bruger API’er på højt niveau, der leveres af udvidelsespunktet og systemet, så de behøver aldrig at bekymre sig om de underliggende kommunikationsmekanismer.

Livscyklus

App-udvidelser har en anden livscyklus end iOS-apps. Værtsappen starter udvidelsens livscyklus som et svar på en brugerhandling. Derefter instantierer systemet app-udvidelsen og opretter en kommunikationskanal mellem dem. Udvidelsens visning vises i værtsappens kontekst ved hjælp af de elementer, der er modtaget i værtsappens anmodning. Når udvidelsens visning er vist, kan brugeren interagere med den. Som svar på brugerens handling afslutter udvidelsen værtsappens anmodning ved straks at udføre/afbryde opgaven eller, om nødvendigt, iværksætte en baggrundsproces til at udføre den. Umiddelbart herefter nedbryder værtsappen udvidelsens visning, og brugeren vender tilbage til sin tidligere kontekst i værtsappen. Resultaterne fra udførelsen af denne proces kan returneres til værtsappen, når processen er afsluttet. Udvidelsen bliver normalt afsluttet kort efter, at den har afsluttet den anmodning, den har modtaget fra værtsappen (eller starter en baggrundsproces for at udføre den).

Systemet åbner udvidelsen af en brugers handling fra værtsappen, udvidelsen viser brugergrænsefladen, udfører noget arbejde og returnerer data til værtsappen (hvis det er passende for udvidelsens type). Den indeholdende app kører ikke engang, mens dens udvidelse kører.

Skabelse af en app-udvidelse – praktisk eksempel ved hjælp af Udvidelsen i dag

Udvidelserne i dag, også kaldet widgets, er placeret i Notifikationscenterets Visning i dag. De er en fantastisk måde til at præsentere et opdateret indhold for brugeren (f.eks. vise vejrforhold) eller udføre hurtige opgaver (f.eks. markere de ting, der er udført i en to-do-liste-apps widget). Jeg er nødt til at påpege her, at tastaturindtastning ikke understøttes.

Lad os oprette en Today-udvidelse, der viser de mest opdaterede oplysninger fra vores app (kode på GitHub). For at kunne køre denne kode skal du sikre dig, at du har (gen)konfigureret App Group for projektet (vælg dit udviklingsteam, husk at App Group-navnet skal være unikt, og følg Xcodes instruktioner).

Skabelse af en ny widget

Som vi sagde før, er app-udvidelserne ikke selvstændige apps. Vi har brug for en indeholdende app, som vi vil bygge app-udvidelsen på. Når vi har vores containing app, vælger vi at tilføje et nyt target ved at navigere til File -> New -> Target i Xcode. Herfra vælger vi skabelonen til vores nye target for at tilføje en Today Extension.

I det næste trin kan vi vælge vores Product Name. Det er det navn, der vil blive vist i Notification Center’s Today-visning. Der er også en mulighed for at vælge sprog mellem Swift og Objective-C i dette trin. Med afslutningen af disse trin opretter Xcode en Today-skabelon, som indeholder standard header- og implementeringsfiler for hovedklassen (med navnet TodayViewController) med Info.plist-fil og en grænsefladefil (en storyboard- eller .xib-fil). Info.plist-filen ser som standard således ud:

Hvis du ikke ønsker at bruge det storyboard, der leveres af skabelonen, skal du fjerne NSExtensionMainStoryboard-nøglen og tilføje NSExtensionPrincipalClass-nøglen med navnet på din visionscontroller som værdi.

En widget i dag skal:

  • sikre, at indholdet altid ser aktuelt ud
  • reagerer hensigtsmæssigt på brugerinteraktioner
  • performere godt (iOS-widgets skal bruge hukommelsen klogt, ellers bliver de afsluttet af systemet)

Deling af data og en delt container

App-udvidelsen og dens indeholdende app har begge adgang til de delte data i deres privat definerede delte container –

  • hvilket er en måde for en indirekte kommunikation mellem den indeholdende app og udvidelsen.

    Er du ikke bare vild med, hvordan Apple gør disse ting så “enkle”? 🙂

    Deling af data via NSUserDefaults er enkelt og et almindeligt anvendelsestilfælde. Som standard bruger udvidelsen og den indeholdende app separate NSUserDefaults-datasæt og kan ikke få adgang til hinandens containere. For at ændre denne adfærd har iOS indført App Groups. Når du har aktiveret appgrupper på den indeholdende app og udvidelsen, skal du i stedet for at bruge bruge initWithSuiteName:@"group.yourAppGroupName"] til at få adgang til den samme delte beholder.

    Opdatering af widget

    For at sikre, at indholdet altid er opdateret, indeholder Today-udvidelsen et API til at administrere en widgets tilstand og håndtere indholdsopdateringer. Systemet optager lejlighedsvis snapshots af widgets visning, så når widgeten bliver synlig, vises det seneste snapshot, indtil det erstattes af en live-version af visningen. En efterlevelse af NCWidgetProviding-protokollen er vigtig for opdatering af en widgets tilstand, før der tages et snapshot. Når widgeten modtager widgetPerformUpdateWithCompletionHandler:-opkaldet, skal widgets visning opdateres med det seneste indhold, og afslutningshåndteringen skal kaldes med en af følgende konstanter til at beskrive resultatet af opdateringen:

    • NCUpdateResultNewData – Det nye indhold kræver en ny tegning af visningen
    • NCUpdateResultNoDate – Widgeten kræver ikke opdatering
    • NCUpdateResultFailed – Der opstod en fejl under opdateringsprocessen

    Kontrol af, hvornår widgeten kan vises

    For at kontrollere, hvornår en widget vises, skal du bruge setHasContent:forWidgetWithBundleIdentifier:-metoden fra NCWidgetController-klassen. Med denne metode kan du angive tilstanden af widgets indhold. Den kan kaldes fra widgeten eller fra den app, der indeholder den (hvis den er aktiv). Du kan sende et NO– eller et YES-flag til denne metode, der definerer, at widgets indhold er klar eller ej. Hvis indholdet ikke er klar, viser iOS ikke din widget, når Visning i dag åbnes.

    Opening af den indeholdende app fra widgetten

    Vidgetten Visning i dag er den eneste udvidelse, der kan anmode om at åbne sin indeholdende app ved at kalde openURL:completionHandler:-metoden. For at sikre, at den indeholdende app åbnes på en måde, der giver mening i forbindelse med brugerens aktuelle opgave, bør der defineres et brugerdefineret URL-skema (som både widgeten og den indeholdende app kan bruge).

     completionHandler:nil];

    UI Overvejelser

    Når du designer din widget, skal du drage fordel af UIVisualEffectView-klassen og huske på, at de visninger, der skal være slørede/vibrante, skal tilføjes til contentView og ikke direkte til UIVisualEffectView. Widgets (der er i overensstemmelse med NCWidgetProviding-protokollen) bør indlæse cachede tilstande i viewWillAppear: for at matche visningens tilstand fra sidste viewWillDisappear: og derefter gå jævnt over til de nye data, når de ankommer, hvilket ikke er tilfældet med en normal view controller (UI er opsat i viewDidLoad og håndterer animationer og indlæsning af data i viewWillAppear). Widgets bør være designet til at udføre en opgave eller åbne den indeholdende app med et enkelt tryk. Tastaturindtastning er ikke tilgængelig inden for en widget. Det betyder, at enhver brugergrænseflade, der kræver tekstindtastning, ikke bør anvendes.

    Det er ikke muligt at tilføje rulninger i en widget, både vertikale og horisontale rulninger. Eller mere præcist, tilføjelse af en rullevisning er mulig, men rulning fungerer ikke. Vandrette rulningsbevægelser i en rullevisning i udvidelsen Today vil blive opsnappet af notifikationscenteret, hvilket vil medføre rulning fra Today til notifikationscenteret. Hvis du ruller lodret i en rullevisning i en Today-udvidelse, afbrydes rulningen af Today-visningen.

    Tekniske noter

    Her vil jeg påpege nogle vigtige ting, som du skal være opmærksom på, når du opretter en App Extension.

    Funktioner, der er fælles for alle udvidelser

    Følgende punkter gælder for alle udvidelser:

    • SharedApplication-objektet er forbudt: App-udvidelser kan ikke få adgang til et sharedApplication-objekt eller bruge nogen af de metoder, der er relateret til dette objekt.

    • Kamera og mikrofon er forbudt område: App-udvidelser kan ikke få adgang til kameraet eller mikrofonen på enheden (men dette gælder ikke for alle hardwareelementer). Dette er et resultat af, at nogle API’er ikke er tilgængelige. Hvis du vil have adgang til nogle hardwareelementer i app-udvidelsen, skal du kontrollere, om dets API er tilgængeligt for app-udvidelser eller ej (med kontrollen af API-tilgængelighed beskrevet ovenfor).

    • De fleste baggrundsopgaver er forbudt adgang: App-udvidelser kan ikke udføre langvarige baggrundsopgaver, undtagen initiering af uploads eller downloads, som behandles nedenfor.

    • AirDrop er ikke tilladt: App-udvidelser kan ikke modtage (men kan sende) data ved hjælp af AirDrop.

    Opload/download i baggrunden

    Den eneste opgave, der kan udføres i baggrunden, er upload/download ved hjælp af NSURLSession object.

    Når upload/download-opgaven er påbegyndt, kan udvidelsen fuldføre værtsappens anmodning og afsluttes uden nogen effekt på opgavens resultat. Hvis udvidelsen ikke kører på det tidspunkt, hvor baggrundsopgaven afsluttes, starter systemet den indeholdende app i baggrunden, og appens delegatmetode application:handleEventsForBackgroundURLSession:completionHandler: kaldes.

    Den app, hvis udvidelse initierer en baggrundsopgave NSURLSession, skal have oprettet en delt container, som både den indeholdende app og dens udvidelse kan få adgang til.

    Sørg for at oprette forskellige baggrundssessioner for den indeholdende app og hver af dens app-udvidelser (hver baggrundssession skal have en unik identifikator). Dette er vigtigt, fordi kun én proces kan bruge en baggrundssession ad gangen.

    Action vs. Share

    Forskellene mellem Action- og Share-udvidelserne er ikke helt tydelige fra en koders synspunkt, fordi de i praksis ligner hinanden meget. Xcodes skabelon for Share-udvidelsesmålet bruger SLComposeServiceViewController, som giver en standardbrugergrænseflade til sammensætningsvisning, som du kan bruge til social deling, men det er ikke påkrævet. En share-udvidelse kan også arve direkte fra UIViewController for at få et helt tilpasset design, på samme måde som en Action-udvidelse kan arve fra SLComposeServiceViewController.

    Forskellene mellem disse to typer udvidelser ligger i, hvordan de er beregnet til at blive brugt. Med Action-udvidelsen kan du bygge en udvidelse uden egen brugergrænseflade (f.eks. en udvidelse, der bruges til at oversætte den valgte tekst og returnere oversættelsen til værtsappen). Med Share-udvidelsen kan du dele kommentarer, fotos, videoer, lyd, links og meget mere direkte fra værtsappen. UIActivityViewController driver både Action- og Share-udvidelser, hvor Share-udvidelser præsenteres som farveikoner i den øverste række, og action-udvidelserne præsenteres som monokrome ikoner i den nederste række (billede 2.1).

    Forbidden APIs

    API’er, der er markeret i headerfilerne med makroen NS_EXTENSION_UNAVAILABLE eller lignende makro for manglende tilgængelighed, kan ikke bruges (f.eks: Hvis du deler kode mellem en app og en udvidelse, skal du huske på, at selv henvisning til et API, der ikke er tilladt for app-udvidelsen, vil føre til, at din app afvises fra App Store. Du kan vælge at håndtere dette ved at refaktorisere de delte klasser til hierarkier med en fælles overordnet klasse og forskellige underklasser for forskellige mål.En anden måde er at bruge præprocessoren ved #ifdef kontroller. Da der stadig ikke er indbygget target conditional, skal du oprette din egen.

    En anden god måde at gøre dette på er ved at oprette din egen embedded framework. Bare sørg for, at det ikke vil indeholde nogen API’er, der ikke er tilgængelige for udvidelser. Hvis du vil konfigurere en app-udvidelse til brug af en indlejret ramme, skal du navigere til målets opbygningsindstillinger og indstille indstillingen “Require Only App-Extension-Safe API” til Ja. Når du konfigurerer Xcode-projektet, skal “Frameworks” vælges som destination for den indlejrede ramme i opbygningsfasen Copy Files (kopier filer). Hvis du vælger destinationen “SharedFrameworks”, vil din indsendelse blive afvist af App Store.

    En note om bagudkompatibilitet

    Selv om app-udvidelser kun har været tilgængelige siden iOS 8, kan du gøre din indeholdende app tilgængelig for de tidligere iOS-versioner.

    Apple Human Interface Compliance

    Hold øje med Apples retningslinjer for iOS Human Interface Guidelines, når du designer en app-udvidelse. Du skal sikre, at din appudvidelse er universel, uanset hvilken enhed din indeholdende app understøtter. For at sikre, at app-udvidelsen er universel, skal du bruge indstillingen for opbygning af målrettet enhedsfamilie i Xcode, hvor du angiver værdien “iPhone/iPad” (nogle gange kaldet universel).

    Slutning

    App-udvidelser har helt klart den mest synlige indvirkning i iOS 8. Da 79 % af enhederne allerede bruger iOS 8 (som målt af App Store den 13. april 2015), er app-udvidelserne utrolige funktioner, som apps bør drage fordel af. Med kombinationen af API’ens begrænsninger og måden at dele data mellem udvidelserne og deres indeholdende app ser det ud til, at Apple har formået at imødegå en af de største klager over platformen uden at gå på kompromis med sikkerhedsmodellen. Der er stadig ingen måde, hvorpå tredjepartsapps kan dele deres data direkte med hinanden. Selv om dette er et meget nyt koncept, ser det meget lovende ud.

Skriv et svar

Din e-mailadresse vil ikke blive publiceret.