app iOS

[AEM headless as a Cloud Service]{class="badge informative"}

Le applicazioni di esempio sono un ottimo modo per esplorare le funzionalità headless di Adobe Experience Manager (AEM). Questa applicazione iOS illustra come eseguire query sui contenuti che utilizzano le API GraphQL dell’AEM utilizzando query persistenti.

App iOS SwiftUI con AEM headless

Visualizza il codice sorgente in GitHub

Prerequisiti prerequisites

I seguenti strumenti devono essere installati localmente:

Requisiti AEM

L’applicazione iOS funziona con le seguenti opzioni di distribuzione dell’AEM. Tutte le distribuzioni richiedono l'installazione del sito WKND v3.0.0+.

L'applicazione iOS è progettata per connettersi a un ambiente AEM Publish, tuttavia può creare contenuto da AEM Author se l'autenticazione viene fornita nella configurazione dell'applicazione iOS.

Come usare

  1. Clona l'archivio adobe/aem-guides-wknd-graphql:

    code language-shell
    $ git clone git@github.com:adobe/aem-guides-wknd-graphql.git
    
  2. Apri Xcode e la cartella ios-app

  3. Modificare il file Config.xcconfig e aggiornare AEM_SCHEME e AEM_HOST in modo che corrispondano al servizio Publish AEM di destinazione.

    code language-plain
    // The http/https protocol scheme used to access the AEM_HOST
    AEM_SCHEME = https
    // Target hostname for AEM environment, do not include http:// or https://
    AEM_HOST = publish-p123-e456.adobeaemcloud.com
    

    Se ci si connette a AEM Author, aggiungere AEM_AUTH_TYPE e le proprietà di autenticazione di supporto a Config.xcconfig.

    Autenticazione di base

    AEM_USERNAME e AEM_PASSWORD autenticano un utente AEM locale con accesso al contenuto GraphQL WKND.

    code language-plain
    AEM_AUTH_TYPE = basic
    AEM_USERNAME = admin
    AEM_PASSWORD = admin
    

    Autenticazione token

    AEM_TOKEN è un token di accesso che esegue l'autenticazione a un utente AEM con accesso al contenuto WKND GraphQL.

    code language-plain
    AEM_AUTH_TYPE = token
    AEM_TOKEN = abcd...0123
    
  4. Crea l’applicazione utilizzando Xcode e distribuisci l’app sul simulatore iOS

  5. Nell’applicazione deve essere visualizzato un elenco di avventure dal sito WKND. Selezionando un’avventura si aprono i relativi dettagli. Nella vista a elenco avventure, seleziona per aggiornare i dati dall’AEM.

Il codice

Di seguito è riportato un riepilogo di come viene creata l’applicazione iOS, di come si connette a AEM Headless per recuperare contenuti utilizzando query persistenti di GraphQL e di come vengono presentati tali dati. Il codice completo si trova su GitHub.

Query persistenti

Seguendo le best practice di AEM Headless, l’applicazione iOS utilizza query persistenti AEM GraphQL per eseguire query sui dati di avventura. L’applicazione utilizza due query persistenti:

  • Query persistente wknd/adventures-all, che restituisce tutte le avventure in AEM con un set abbreviato di proprietà. Questa query persistente guida l’elenco di avventure della visualizzazione iniziale.
# Retrieves a list of all Adventures
#
# Optional query variables:
# - { "offset": 10 }
# - { "limit": 5 }
# - {
#    "imageFormat": "JPG",
#    "imageWidth": 1600,
#    "imageQuality": 90
#   }

query ($offset: Int, $limit: Int, $sort: String, $imageFormat: AssetTransformFormat=JPG, $imageWidth: Int=1200, $imageQuality: Int=80) {
  adventureList(
    offset: $offset
    limit: $limit
    sort: $sort
    _assetTransform: {
      format: $imageFormat
      width: $imageWidth
      quality: $imageQuality
      preferWebp: true
  }) {
    items {
      _path
      slug
      title
      activity
      price
      tripLength
      primaryImage {
        ... on ImageRef {
          _path
          _dynamicUrl
        }
      }
    }
  }
}
  • Query persistente wknd/adventure-by-slug, che restituisce una singola avventura di slug (una proprietà personalizzata che identifica in modo univoco un'avventura) con un set completo di proprietà. Questa query persistente attiva le visualizzazioni dei dettagli dell’avventura.
query ($slug: String!, $imageFormat:AssetTransformFormat=JPG, $imageSeoName: String, $imageWidth: Int=1200, $imageQuality: Int=80) {
  adventureList(
    filter: {slug: {_expressions: [{value: $slug}]}}
    _assetTransform: {
      format: $imageFormat
      seoName: $imageSeoName
      width: $imageWidth
      quality: $imageQuality
      preferWebp: true
  }) {
    items {
      _path
      title
      slug
      activity
      adventureType
      price
      tripLength
      groupSize
      difficulty
      price
      primaryImage {
        ... on ImageRef {
          _path
          _dynamicUrl
        }
      }
      description {
        json
        plaintext
        html
      }
      itinerary {
        json
        plaintext
        html
      }
    }
    _references {
      ... on AdventureModel {
        _path
        slug
        title
        price
        __typename
      }
    }
  }
}

Esegui query persistente GraphQL

Le query persistenti dell’AEM vengono eseguite su HTTP GET e pertanto non è possibile utilizzare le librerie GraphQL comuni che utilizzano HTTP POST, come Apollo. Creare invece una classe personalizzata che esegua le richieste HTTP di query persistenti a AEM GET.

AEM/Aem.swift crea un'istanza della classe Aem utilizzata per tutte le interazioni con AEM Headless. Il pattern è:

  1. A ogni query persistente corrisponde un func pubblico (ad es. getAdventures(..) o getAdventureBySlug(..)) le visualizzazioni dell'applicazione iOS vengono richiamate per ottenere i dati relativi all'avventura.

  2. Il func pubblico chiama un func privato makeRequest(..) che richiama una richiesta HTTP GET asincrona a AEM Headless e restituisce i dati JSON.

  3. Ogni funzione pubblica decodifica quindi i dati JSON ed esegue tutte le verifiche o trasformazioni necessarie, prima di restituire i dati di Adventure alla visualizzazione.

    • I dati JSON GraphQL dell'AEM vengono decodificati utilizzando le strutture/classi definite in AEM/Models.swift, che corrispondono agli oggetti JSON restituiti dall'AEM headless.
    /// # getAdventures(..)
    /// Returns all WKND adventures using the `wknd-shared/adventures-all` persisted query.
    /// For this func call to work, the `wknd-shared/adventures-all` query must be deployed to the AEM environment/service specified by the host.
    ///
    /// Since HTTP requests are async, the completion syntax is used.
    func getAdventures(params: [String:String], completion: @escaping ([Adventure]) ->  ()) {

        let request = makeRequest(persistedQueryName: "wknd-shared/adventures-all", params: params)

        URLSession.shared.dataTask(with: request) { (data, response, error) in
            if ((error) != nil) {
                print("Unable to connect to AEM GraphQL endpoint")
                completion([])
            } else if (!data!.isEmpty) {
                let adventures = try! JSONDecoder().decode(Adventures.self, from: data!)
                DispatchQueue.main.async {
                    completion(adventures.data.adventureList.items)
                }
            }
        }.resume();
    }

    ...

    /// #makeRequest(..)
    /// Generic method for constructing and executing AEM GraphQL persisted queries
    private func makeRequest(persistedQueryName: String, params: [String: String] = [:]) -> URLRequest {
        // Encode optional parameters as required by AEM
        let persistedQueryParams = params.map { (param) -> String in
            encode(string: ";\(param.key)=\(param.value)")
        }.joined(separator: "")

        // Construct the AEM GraphQL persisted query URL, including optional query params
        let url: String = "\(self.scheme)://\(self.host)/graphql/execute.json/" + persistedQueryName + persistedQueryParams;

        var request = URLRequest(url: URL(string: url)!);

        // Add authentication to the AEM GraphQL persisted query requests as defined by the iOS application's configuration
        request = addAuthHeaders(request: request)

        return request
    }

    ...

Modelli di dati di risposta GraphQL

iOS preferisce mappare gli oggetti JSON ai modelli di dati tipizzati.

src/AEM/Models.swift definisce le strutture e classi Swift decodificabili mappate alle risposte JSON AEM restituite dalle risposte JSON AEM.

Viste

SwiftUI viene utilizzato per le varie visualizzazioni nell’applicazione. Apple fornisce un tutorial introduttivo per creare elenchi e navigare con SwiftUI.

  • WKNDAdventuresApp.swift

    La voce dell'applicazione e include AdventureListView il cui gestore eventi .onAppear è utilizzato per recuperare tutti i dati di Adventures tramite aem.getAdventures(). L'oggetto aem condiviso è inizializzato qui ed esposto ad altre visualizzazioni come OggettoAmbiente.

  • Views/AdventureListView.swift

    Visualizza un elenco di avventure (in base ai dati di aem.getAdventures()) e visualizza una voce di elenco per ogni avventura utilizzando AdventureListItemView.

  • Views/AdventureListItemView.swift

    Visualizza ogni elemento nell'elenco Avventure (Views/AdventureListView.swift).

  • Views/AdventureDetailView.swift

    Visualizza i dettagli di un'avventura, tra cui il titolo, la descrizione, il prezzo, il tipo di attività e l'immagine primaria. Questa visualizzazione richiede all'AEM i dettagli completi dell'avventura utilizzando aem.getAdventureBySlug(slug: slug), dove il parametro slug viene passato in base alla riga dell'elenco di selezione.

Immagini remote

Le immagini a cui si fa riferimento nei frammenti di contenuto dell’avventura sono servite dall’AEM. Questa app iOS utilizza il campo percorso _dynamicUrl nella risposta di GraphQL e aggiunge i prefissi AEM_SCHEME e AEM_HOST per creare un URL completo. Se si sviluppa in base all'SDK AE, _dynamicUrl restituisce null, quindi per lo sviluppo il fallback al campo _path dell'immagine.

Se ti connetti a risorse protette su AEM che richiedono un’autorizzazione, è necessario aggiungere le credenziali anche alle richieste di immagini.

SDWebImageSwiftUI e SDWebImage vengono utilizzati per caricare le immagini remote da AEM che popolano l'immagine Adventure nelle visualizzazioni AdventureListItemView e AdventureDetailView.

La classe aem (in AEM/Aem.swift) facilita l'utilizzo delle immagini AEM in due modi:

  1. aem.imageUrl(path: String) viene utilizzato nelle visualizzazioni per anteporre lo schema AEM e ospitare il percorso dell'immagine, creando un URL completo.

    code language-swift
    // adventure.image() => /adobe/dynamicmedia/deliver/dm-aid--741ed388-d5f8-4797-8095-10c896dc9f1d/example.jpg?quality=80&preferwebp=true
    
    let imageUrl = aem.imageUrl(path: adventure.image())
    // imageUrl => https://publish-p123-e456.adobeaemcloud.com/adobe/dynamicmedia/deliver/dm-aid--741ed388-d5f8-4797-8095-10c896dc9f1d/example.jpg?quality=80&preferwebp=true
    
  2. convenience init(..) in Aem ha impostato le intestazioni di autorizzazione HTTP nella richiesta HTTP dell'immagine, in base alla configurazione delle applicazioni iOS.

    • Se è configurata l'autenticazione di base, l'autenticazione di base viene associata a tutte le richieste di immagini.
    code language-swift
    /// AEM/Aem.swift
    ///
    /// # Basic authentication init
    /// Used when authenticating to AEM using local accounts (basic auth)
    convenience init(scheme: String, host: String, username: String, password: String) {
        ...
    
        // Add basic auth headers to all Image requests, as they are (likely) protected as well
        SDWebImageDownloader.shared.setValue("Basic \(encodeBasicAuth(username: username, password: password))", forHTTPHeaderField: "Authorization")
    }
    
    • Se è configurata l'autenticazione token, l'autenticazione token viene associata a tutte le richieste di immagini.
    code language-swift
    /// AEM/Aem.swift
    ///
    /// # Token authentication init
    ///  Used when authenticating to AEM using token authentication (Dev Token or access token generated from Service Credentials)
    convenience init(scheme: String, host: String, token: String) {
        ...
    
        // Add token auth headers to all Image requests, as they are (likely) protected as well
        SDWebImageDownloader.shared.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
    }
    
    • Se non è configurata alcuna autenticazione, non verrà associata alcuna autenticazione alle richieste di immagini.

Un approccio simile può essere utilizzato con AsyncImage nativo per SwiftUI. AsyncImage è supportato in iOS 15.0+.

Risorse aggiuntive

recommendation-more-help
e25b6834-e87f-4ff3-ba56-4cd16cdfdec4