Een Tutorial over iOS 8 App Extensions

Weinig mensen hadden het eerder geprobeerd (kijk maar), maar het was Apple die met de eerste iPhone bepaalde hoe een smartphone en een mobiel OS eruit moesten zien. Apple zorgde voor een ongelooflijke doorbraak op het gebied van hardware en gebruikerservaring. We vergeten echter vaak dat zij ook de standaard hebben gezet in hoe een mobiel OS moet werken, en hoe Smartphone applicaties moeten worden gemaakt.

Het bouwen van betonnen muren tussen de applicaties, waardoor ze volledig geïsoleerd en niet op de hoogte van elkaar waren, was de beste methode om ze veilig te houden en hun gegevens te beschermen. Alle activiteiten werden nauwlettend in de gaten gehouden door iOS, en er was slechts een handjevol acties die een app buiten zijn bereik kon uitvoeren.

“Onthouding is de beste bescherming!” – maar waar is de lol daarvan?

Het heeft even geduurd; te lang als je het mij vraagt, maar met iOS 8 heeft Apple besloten om wat lol te maken. iOS 8 introduceerde een nieuw concept genaamd App Extensions. Deze nieuwe functie brak de muren tussen de applicaties niet af, maar opende wel een paar deuren die zacht maar tastbaar contact tussen sommige apps mogelijk maakten. De laatste update heeft iOS-ontwikkelaars een optie gegeven om het iOS-ecosysteem aan te passen, en we staan te popelen om ook dit pad te zien opengaan.

Wat zijn de iOS 8 App Extensions en hoe werken ze?

In eenvoudige bewoordingen biedt iOS 8 App Extensions een nieuwe methode voor interactie met je applicatie, zonder deze te starten of op het scherm te tonen.

Zoals verwacht heeft Apple ervoor gezorgd dat ze overal bovenop zitten, dus er zijn slechts een handvol nieuwe ingangen die je applicatie kan bieden:

  • Vandaag (ook wel een widget genoemd) – een extensie die wordt weergegeven in de Vandaag-weergave van het Berichtencentrum toont beknopte informatie en maakt het mogelijk om snelle taken uit te voeren.
  • Delen – een extensie waarmee uw app inhoud kan delen met gebruikers op sociale netwerken en andere diensten voor delen.
  • Actie – een extensie waarmee aangepaste actieknoppen in het actieblad kunnen worden gemaakt waarmee gebruikers inhoud die afkomstig is uit een host-app kunnen bekijken of omzetten.
  • Fotobewerking – een extensie waarmee gebruikers een foto of video kunnen bewerken binnen de app Foto’s.
  • Documentprovider – een extensie die wordt gebruikt om andere apps toegang te geven tot de documenten die door uw app worden beheerd.
  • Aangepast toetsenbord – een extensie die het systeemtoetsenbord vervangt.

App-extensies zijn geen op zichzelf staande apps. Ze bieden uitgebreide functionaliteit van de app (die kan worden benaderd vanuit andere apps, de zogenaamde host-apps) die is bedoeld om efficiënt te zijn en gericht op een enkele taak. Ze hebben hun eigen binary, eigen code signature, en eigen set van elementen, maar worden geleverd via de App Store als onderdeel van de binary van de bevattende app. Eén (bevattende) app kan meer dan één extensie hebben. Zodra de gebruiker een app installeert die extensies heeft, zullen deze beschikbaar zijn in heel iOS.

Laten we eens kijken naar een voorbeeld: Een gebruiker vindt een foto met behulp van Safari, drukt op de knop delen en kiest uw applicatie extensie voor het delen. Safari “praat” met het sociale iOS-framework, dat de extensie laadt en presenteert. De code van de extensie wordt uitgevoerd, gegevens worden doorgegeven via de geïnstalleerde communicatiekanalen van het systeem, en als de taak is voltooid, wordt de extensieweergave door Safari afgebroken. Kort daarna beëindigt het systeem het proces, en uw toepassing is nooit op het scherm verschenen. Toch heeft het een functie voor het delen van foto’s voltooid.

iOS is er met behulp van interproces-communicatie verantwoordelijk voor dat de host-app en een app-extensie met elkaar kunnen samenwerken. De ontwikkelaars gebruiken API’s op hoog niveau die door het extensiepunt en het systeem worden geleverd, zodat ze zich nooit zorgen hoeven te maken over de onderliggende communicatiemechanismen.

Life Cycle

App-extensies hebben een andere levenscyclus dan de iOS-apps. De host-app start de levenscyclus van de extensie als reactie op een actie van een gebruiker. Dan instantieert het systeem de app-extensie en zet een communicatiekanaal tussen hen op. De weergave van de extensie wordt weergegeven binnen de context van de host-app met behulp van de items die zijn ontvangen in het verzoek van de host-app. Zodra de weergave van de extensie wordt weergegeven, kan de gebruiker ermee interageren. Als reactie op de actie van de gebruiker rondt de extensie het verzoek van de host-app af door de taak onmiddellijk uit te voeren/te annuleren of, indien nodig, een achtergrondproces te starten om de taak uit te voeren. Direct daarna breekt de host-app de weergave van de extensie af en keert de gebruiker terug naar zijn vorige context binnen de host-app. De resultaten van het uitvoeren van dit proces kunnen worden teruggegeven aan de host-app zodra het proces is voltooid. De extensie wordt gewoonlijk beëindigd kort nadat het het van de host-app ontvangen verzoek heeft voltooid (of start een achtergrondproces om het uit te voeren).

Het systeem opent de extensie van een gebruikersactie vanuit de host-app, de extensie toont de UI, voert wat werk uit en retourneert gegevens aan de host-app (als dat past bij het type van de extensie). De app die de extensie bevat, draait niet eens terwijl de extensie actief is.

Een app-extensie maken – Hands-on voorbeeld met de Vandaag-extensie

De Vandaag-extensies, ook wel widgets genoemd, bevinden zich in de Vandaag-weergave van het Berichtencentrum. Ze zijn een geweldige manier om actuele inhoud aan de gebruiker te presenteren (zoals het weer) of om snelle taken uit te voeren (zoals het markeren van de dingen die gedaan zijn in een to-do lijst app’s widget). Ik moet er hier op wijzen dat invoer via het toetsenbord niet wordt ondersteund.

Laten we een Vandaag-extensie maken die de meest recente informatie van onze app weergeeft (code op GitHub). Om deze code te kunnen uitvoeren, moet u de app-groep voor het project (opnieuw) configureren (selecteer uw ontwikkelteam, houd er rekening mee dat de naam van de app-groep uniek moet zijn en volg de instructies van Xcode).

Een nieuwe widget maken

Zoals we al eerder zeiden, zijn de app-extensies geen op zichzelf staande apps. We hebben een containing app nodig waarop we de app-extensie bouwen. Zodra we onze containing app hebben, kiezen we om een nieuw doel toe te voegen door te navigeren naar Bestand -> Nieuw -> Doel in Xcode. Van hieruit kiezen we het sjabloon voor ons nieuwe target om een Today Extension toe te voegen.

In de volgende stap kunnen we onze Product Name kiezen. Dat is de naam die zal verschijnen in de Vandaag-weergave van het Berichtencentrum. Er is ook een optie om de taal te kiezen tussen Swift en Objective-C in deze stap. Als je klaar bent met deze stappen, maakt Xcode een Today-sjabloon, met standaard header- en implementatiebestanden voor de hoofdklasse (met de naam TodayViewController) met een Info.plist-bestand en een interfacebestand (een storyboard of .xib-bestand). Het Info.plist bestand ziet er standaard als volgt uit:

Als je het storyboard dat door het template wordt geleverd niet wilt gebruiken, verwijder dan de NSExtensionMainStoryboard sleutel en voeg de NSExtensionPrincipalClass sleutel toe met de naam van je view controller als waarde.

Een Vandaag-widget moet:

  • zorgen dat de inhoud er altijd actueel uitziet
  • op de juiste manier reageren op interacties van gebruikers
  • goed presteren (iOS-widgets moeten geheugen verstandig gebruiken, anders worden ze door het systeem beëindigd)

Gegevens delen en een gedeelde container

De app-extensie en de bevattende app hebben beide toegang tot de gedeelde gegevens in hun privaat gedefinieerde gedeelde container -. wat een manier is van indirecte communicatie tussen de bevattende app en de extensie.

Houdt u er niet van hoe Apple deze dingen zo “eenvoudig” maakt?

Het delen van gegevens via NSUserDefaults is eenvoudig en een veelvoorkomend gebruik. Standaard gebruiken de extensie en de bijbehorende app afzonderlijke NSUserDefaults-gegevenssets, en hebben geen toegang tot elkaars containers. Om dit gedrag te veranderen, introduceerde iOS App Groups. Na het inschakelen van app groups op de bevattende app en de extensie, in plaats van gebruikt u initWithSuiteName:@"group.yourAppGroupName"] voor toegang tot dezelfde gedeelde container.

Widget bijwerken

Om ervoor te zorgen dat de inhoud altijd actueel is, biedt de Today-extensie een API voor het beheren van de status van een widget en het verwerken van inhoudsupdates. Het systeem maakt af en toe snapshots van de weergave van de widget, dus wanneer de widget zichtbaar wordt, wordt de meest recente snapshot getoond totdat deze wordt vervangen door een live versie van de weergave. Een conformatie aan het NCWidgetProviding-protocol is belangrijk voor het bijwerken van de toestand van een widget voordat een momentopname wordt gemaakt. Zodra de widget de widgetPerformUpdateWithCompletionHandler:-aanroep ontvangt, moet de view van de widget worden bijgewerkt met de meest recente inhoud en moet de completion handler worden aangeroepen met een van de volgende constanten voor het beschrijven van het resultaat van de bijwerking:

  • NCUpdateResultNewData – De nieuwe inhoud vereist het opnieuw tekenen van de view
  • NCUpdateResultNoDate – De widget hoeft niet te worden bijgewerkt
  • NCUpdateResultFailed – Er is een fout opgetreden tijdens het update proces

Controlling When the Widget Is Viewable

Om te bepalen wanneer een widget wordt weergegeven, gebruikt u de setHasContent:forWidgetWithBundleIdentifier: methode uit de NCWidgetController klasse. Deze methode laat u de status van de inhoud van de widget specificeren. De methode kan worden aangeroepen vanuit de widget of vanuit de app die de widget bevat (als die actief is). U kunt een NO of een YES vlag aan deze methode doorgeven, die definieert of de widget inhoud gereed is of niet. Als de inhoud niet klaar is, zal iOS uw widget niet weergeven wanneer de Vandaag-weergave wordt geopend.

Openen van de bevattende app vanuit de widget

De Vandaag-widget is de enige extensie die kan vragen om de bevattende app te openen door de methode openURL:completionHandler: aan te roepen. Om ervoor te zorgen dat de bevattende app wordt geopend op een manier die zinvol is in de context van de huidige taak van de gebruiker, moet een aangepast URL-schema (dat zowel de widget als de bevattende app kunnen gebruiken) worden gedefinieerd.

 completionHandler:nil];

UI Overwegingen

Wanneer u uw widget ontwerpt, maak dan gebruik van de UIVisualEffectView class, waarbij u in gedachten moet houden dat de weergaven die wazig/levendig moeten zijn, moeten worden toegevoegd aan de contentView en niet direct aan de UIVisualEffectView. Widgets (die voldoen aan het NCWidgetProviding protocol) moeten gecachede toestanden laden in viewWillAppear: om overeen te komen met de toestand van de view van de laatste viewWillDisappear: en dan soepel overgaan naar de nieuwe data als die binnenkomt, wat niet het geval is met een normale view controller (UI wordt opgezet in viewDidLoad en handelt animaties en het laden van data af in viewWillAppear). Widgets moeten worden ontworpen voor het uitvoeren van een taak, of het openen van de bijbehorende app met een enkele tik. Het toetsenbord is niet beschikbaar binnen een widget. Dit betekent dat elke UI die tekstinvoer vereist niet moet worden gebruikt.

Het toevoegen van scrolls in een widget, zowel verticaal als horizontaal, is niet mogelijk. Of beter gezegd, het toevoegen van een scrollweergave is mogelijk, maar scrollen zal niet werken. Horizontaal scrollen in een scrollweergave in de Vandaag-extensie wordt onderschept door het meldingscentrum, waardoor er van Vandaag naar het meldingscentrum wordt gescrolld. Verticaal scrollen in een scrollweergave in een Vandaag-extensie wordt onderbroken door scrollen in de Vandaag-weergave.

Technische opmerkingen

Hiernaast wijs ik u op enkele belangrijke zaken die u in gedachten moet houden bij het maken van een App Extensie.

Functies die voor alle Extensies gelden

De volgende zaken zijn waar voor alle extensies:

  • GedeeldApplicatie-object is verboden toegang: App-extensies hebben geen toegang tot een gedeeldApplicatie-object en kunnen geen methoden gebruiken die aan dat object zijn gerelateerd.

  • Camera en microfoon zijn niet toegestaan: App-extensies hebben geen toegang tot de camera of microfoon op het apparaat (maar dit is niet het geval voor alle hardware-elementen). Dit is een gevolg van de onbeschikbaarheid van sommige API’s. Om toegang te krijgen tot sommige hardware-elementen in de app-extensie, moet u controleren of de API beschikbaar is voor app-extensies of niet (met de hierboven beschreven API-beschikbaarheidscontrole).

  • De meeste achtergrondtaken zijn niet toegestaan: App-extensies kunnen geen langlopende achtergrondtaken uitvoeren, behalve het initiëren van uploads of downloads, wat hieronder wordt besproken.

  • AirDrop is off limits: App-extensies kunnen geen gegevens ontvangen (maar wel verzenden) met AirDrop.

Opladen/Downloaden op de achtergrond

De enige taak die op de achtergrond kan worden uitgevoerd, is uploaden/downloaden, met behulp van de NSURLSession object.

Nadat de upload/download-taak is gestart, kan de extensie het verzoek van de host-app voltooien en worden beëindigd zonder enig effect op het resultaat van de taak. Als de extensie niet wordt uitgevoerd op het moment dat de achtergrondtaak wordt voltooid, start het systeem de bevattende app op de achtergrond en wordt de delegatiemethode application:handleEventsForBackgroundURLSession:completionHandler: van de app aangeroepen.

De app waarvan de extensie een achtergrond NSURLSession-taak initieert, moet een gedeelde container hebben ingesteld waartoe zowel de bevattende app als zijn extensie toegang hebben.

Zorg ervoor dat u verschillende achtergrondsessies maakt voor de bevattende app en elk van zijn app-extensies (elke achtergrondsessie moet een unieke identifier hebben). Dit is belangrijk omdat slechts één proces tegelijkertijd een achtergrondsessie kan gebruiken.

Actie vs. Share

De verschillen tussen de Action- en Share-extensies zijn niet helemaal duidelijk vanuit het perspectief van een codeur, omdat ze in de praktijk erg op elkaar lijken. Xcode’s sjabloon voor het doel van de share-extensie gebruikt SLComposeServiceViewController, dat een standaard compose view UI biedt die je kunt gebruiken voor sociaal delen, maar het is niet vereist. Een share-extensie kan ook rechtstreeks erven van UIViewController voor een volledig aangepast ontwerp, op dezelfde manier als een Action-extensie kan erven van SLComposeServiceViewController.

De verschillen tussen deze twee soorten extensies is in hoe ze bedoeld zijn om te worden gebruikt. Met de Action-extensie kunt u een extensie bouwen zonder eigen UI (bijvoorbeeld een extensie die wordt gebruikt voor het vertalen van de geselecteerde tekst en het terugzenden van de vertaling naar de host-app). Met de Share-extensie kunt u opmerkingen, foto’s, video’s, audio, links en meer direct vanuit de host-app delen. De UIActivityViewController stuurt zowel Actie- als Share-extensies aan, waarbij Share-extensies worden weergegeven als kleurenpictogrammen in de bovenste rij en de actie-extensies worden weergegeven als monochrome pictogrammen in de onderste rij (afbeelding 2.1).

Verboden API’s

API’s die in de headerbestanden zijn gemarkeerd met de NS_EXTENSION_UNAVAILABLE macro, of een soortgelijke macro voor niet-beschikbaarheid, kunnen niet worden gebruikt (bijvoorbeeld: HealthKit en EventKit UI frameworks in iOS 8 zijn niet beschikbaar voor gebruik in een app-extensie).

Als u code deelt tussen een app en een extensie, moet u in gedachten houden dat zelfs het verwijzen naar een API die niet is toegestaan voor de app-extensie zal leiden tot afwijzing van uw app uit de App Store. Je kunt dit oplossen door de gedeelde klassen te re-factoren in hiërarchieën, met een gemeenschappelijke parent en verschillende subklassen voor verschillende targets.Een andere manier is om de pre-processor te gebruiken door #ifdef controles. Omdat er nog steeds geen ingebouwde target conditional is, moet je er zelf een maken.

Een andere leuke manier om dit te doen is door je eigen embedded framework te maken. Zorg er alleen voor dat het geen API’s bevat die niet beschikbaar zijn voor extensies. Om een app-extensie te configureren voor het gebruik van een ingesloten framework, navigeer je naar de build-instellingen van het doel en zet je de instelling “Require Only App-Extension-Safe API” op Yes. Bij het configureren van het Xcode project, in de Copy Files build fase, moet “Frameworks” worden gekozen als de bestemming voor het ingesloten framework. Als u de bestemming “SharedFrameworks” kiest, zal uw inzending door de App Store worden afgewezen.

Een opmerking over achterwaartse compatibiliteit

Hoewel app-extensies pas sinds iOS 8 beschikbaar zijn, kunt u uw bevattende app beschikbaar maken voor de eerdere iOS-versies.

Apple Human Interface Compliance

Houd bij het ontwerpen van een app-extensie rekening met de iOS Human Interface Guidelines van Apple. U moet ervoor zorgen dat uw app-extensie universeel is, ongeacht het apparaat dat uw bevattende app ondersteunt. Om ervoor te zorgen dat de app-extensie universeel is, gebruikt u de instelling “Target Device Family Build” in Xcode en geeft u de waarde “iPhone/iPad” op (soms universeel genoemd).

Conclusie

App-extensies hebben zeker de meest zichtbare impact in iOS 8. Aangezien 79% van de apparaten iOS 8 al gebruikt (zoals gemeten door de App Store op 13 april 2015), zijn de app-extensies ongelooflijke functies waar apps hun voordeel mee zouden moeten doen. Met het combineren van de beperkingen van de API en de manier van het delen van gegevens tussen de extensies en hun bevattende app, lijkt het erop dat Apple erin is geslaagd om een van de grootste klachten over het platform aan te pakken zonder afbreuk te doen aan zijn beveiligingsmodel. Er is nog steeds geen manier voor de apps van derden om hun gegevens rechtstreeks met elkaar te delen. Hoewel dit een zeer nieuw concept is, ziet het er veelbelovend uit.

Geef een antwoord

Het e-mailadres wordt niet gepubliceerd.