Der Code
Nachstehend finden Sie eine Zusammenfassung zur Erstellung der iOS-Anwendung, zu ihrer Verbindung mit AEM Headless, um Inhalte mithilfe von GraphQL-persistierten Abfragen abzurufen, und zur Darstellung dieser Daten. Den vollständigen Code finden Sie auf GitHub.
Persistierte Abfragen
Gemäß den Best Practices für AEM Headless verwendet die iOS-Anwendung AEM GraphQL-persistierte Abfragen, um Adventure-Daten abzufragen. Die Anwendung verwendet zwei persistierte Abfragen:
- Die persistierte Abfrage
wknd/adventures-all
gibt alle Adventures in AEM mit einer gekürzten Reihe von Eigenschaften zurück. Diese persistierte Abfrage bestimmt die Erlebnisliste der ersten Ansicht.
# 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
}
}
}
}
}
- Die persistierte Abfrage
wknd/adventure-by-slug
gibt ein einzelnes Adventure durchslug
(eine benutzerdefinierte Eigenschaft, die ein Adventure eindeutig identifiziert) mit einer vollständigen Reihe von Eigenschaften zurück. Diese persistierte Abfrage ermöglicht Detailansichten des Adventures.
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
}
}
}
}
Durchführen einer GraphQL-persistierten Abfrage
Persistierte Abfragen von AEM werden über HTTP-GET ausgeführt. Daher können keine gängigen GraphQL-Bibliotheken, die HTTP-POST verwenden (z. B. Apollo), genutzt werden. Erstellen Sie stattdessen eine benutzerdefinierte Klasse, die persistierte Abfragen über HTTP-GET-Anfragen an AEM ausführt.
AEM/Aem.swift
instanziiert die Aem
-Klasse, die für alle Interaktionen mit AEM Headless verwendet wird. Das Muster lautet:
-
Jede persistierte Abfrage verfügt über eine entsprechende öffentliche Funktion (z. B.
getAdventures(..)
odergetAdventureBySlug(..)
), die von den Ansichten der iOS-Anwendung aufgerufen wird, um Erlebnisdaten zu erhalten. -
Die öffentliche Funktion ruft eine private Funktion
makeRequest(..)
auf, die eine asynchrone HTTP-GET-Anfrage an AEM Headless aufruft und JSON-Daten zurückgibt. -
Jede öffentliche Funktion decodiert dann die JSON-Daten und führt alle erforderlichen Prüfungen oder Umwandlungen durch, bevor die Adventure-Daten an die Ansicht zurückgegeben werden.
- AEM GraphQL-JSON-Daten werden mit den in
AEM/Models.swift
definierten Strukturen/Klassen decodiert, die den von AEM Headless zurückgegebenen JSON-Objekten zugeordnet sind.
- AEM GraphQL-JSON-Daten werden mit den in
/// # 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
}
...
GraphQL-Antwort-Datenmodelle
iOS bevorzugt die Zuordnung von JSON-Objekten zu typisierten Datenmodellen.
src/AEM/Models.swift
definiert die decodierbaren Swift-Strukturen und -Klassen, die den von AEM-JSON-Antworten zurückgegebenen AEM-JSON-Antworten zugeordnet sind.
Ansichten
SwiftUI wird für die verschiedenen Ansichten in der Anwendung verwendet. Apple bietet ein Erste-Schritte-Tutorial für die Erstellung von Listen und Navigation mit SwiftUI.
-
WKNDAdventuresApp.swift
Entspricht dem Einstiegspunkt für die Anwendung und enthält
AdventureListView
, dessen Ereignis-Handler.onAppear
verwendet wird, um alle Adventure-Daten überaem.getAdventures()
abzurufen. Das gemeinsameaem
-Objekt wird hier initialisiert und anderen Ansichten als EnvironmentObject bereitgestellt. -
Views/AdventureListView.swift
Zeigt eine Liste der Adventures (basierend auf den Daten aus
aem.getAdventures()
) sowie ein Listenelement für jedes Adventure mithilfe vonAdventureListItemView
an. -
Views/AdventureListItemView.swift
Zeigt die einzelnen Elemente in der Adventure-Liste an (
Views/AdventureListView.swift
). -
Views/AdventureDetailView.swift
Zeigt Details zu einem Adventure an, einschließlich Titel, Beschreibung, Preis, Aktivitätstyp und Primärbild. In dieser Ansicht werden mit
aem.getAdventureBySlug(slug: slug)
alle Adventure-Details aus AEM abgefragt, wobei derslug
-Parameter basierend auf der ausgewählten Listenzeile übergeben wird.
Remote-Bilder
Bilder, auf die von abenteuerbezogenen Inhaltsfragmenten verwiesen wird, werden von AEM bereitgestellt. Diese iOS-App verwendet das Pfadfeld _dynamicUrl
in der GraphQL-Antwort und stellt AEM_SCHEME
und AEM_HOST
als Präfixe voran, um eine vollständig qualifizierte URL zu erstellen. Bei Entwicklungen gegen das AEM SDK gibt _dynamicUrl
Null zurück. Greifen Sie deshalb zur Entwicklung auf das Feld _path
des Bilds zurück.
Wenn eine Verbindung zu geschützten Ressourcen in AEM hergestellt werden soll, für die eine Autorisierung erforderlich ist, müssen zu Bildanfragen ebenfalls Anmeldeinformationen hinzugefügt werden.
SDWebImageSwiftUI und SDWebImage werden verwendet, um die Remote-Bilder von AEM zu laden, die die Ansichten AdventureListItemView
und AdventureDetailView
mit dem Adventure-Bild aktualisieren.
Die aem
-Klasse (in AEM/Aem.swift
) ermöglicht die Verwendung von AEM-Bildern auf zwei Arten:
-
aem.imageUrl(path: String)
wird in Ansichten verwendet, um das AEM-Schema und den Host dem Bildpfad voranzustellen und so eine vollständig qualifizierte URL zu erstellen.// 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
-
convenience init(..)
inAem
legt HTTP-Autorisierungs-Header für die Bild-HTTP-Anfrage fest, basierend auf der Konfiguration der iOS-Anwendungskonfiguration.- Wenn die Standardauthentifizierung konfiguriert ist, wird diese allen Bildanfragen angehängt.
/// 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") }
- Wenn die Token-Authentifizierung konfiguriert ist, wird diese allen Bildanfragen angehängt.
/// 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") }
- Wenn keine Authentifizierung konfiguriert ist, wird keine Authentifizierung an Bildanfragen angehängt.
Ein ähnlicher Ansatz kann mit der SwiftUI-nativen AsyncImage-Ansicht verwendet werden. AsyncImage
wird ab iOS 15.0 unterstützt.