Beispielanwendungen eignen sich hervorragend, um die Headless-Funktionen von Adobe Experience Manager (AEM) zu erkunden. Diese iOS-Anwendung zeigt, wie Inhalte mithilfe von AEM GraphQL-APIs unter Verwendung persistierter Abfragen abgerufen werden können.
Sie finden den Quell-Code auf GitHub.
Folgende Tools sollten lokal installiert werden:
Die iOS-Anwendung kann mit den folgenden AEM-Bereitstellungsoptionen verwendet werden. Für alle Bereitstellungen muss die WKND Site v3.0.0+ installiert werden.
Die iOS-Anwendung ist für die Verbindung mit einer AEM Publish-Umgebung konzipiert, kann jedoch Inhalte von AEM Author beziehen, wenn die Authentifizierung in der Konfiguration der iOS-Anwendung bereitgestellt wird.
Klonen Sie das Repository adobe/aem-guides-wknd-graphql
:
$ git clone git@github.com:adobe/aem-guides-wknd-graphql.git
Starten Sie Xcode und öffnen Sie den Ordner ios-app
.
Ändern Sie die Datei Config.xcconfig
und aktualisieren Sie AEM_SCHEME
und AEM_HOST
entsprechend Ihrem AEM Publish-Ziel-Service.
// 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
Wenn Sie eine Verbindung zu AEM Author herstellen, fügen Sie AEM_AUTH_TYPE
und unterstützende Authentifizierungseigenschaften zu Config.xcconfig
hinzu.
Standardauthentifizierung
AEM_USERNAME
und AEM_PASSWORD
authentifizieren lokale AEM-Benutzende mit Zugriff auf WKND GraphQL-Inhalte.
AEM_AUTH_TYPE = basic
AEM_USERNAME = admin
AEM_PASSWORD = admin
Token-Authentifizierung
AEM_TOKEN
ist ein Zugriffstoken, das sich bei einer AEM-Benutzerin oder einem AEM-Benutzer mit Zugriff auf WKND GraphQL-Inhalte authentifiziert.
AEM_AUTH_TYPE = token
AEM_TOKEN = abcd...0123
Erstellen Sie die Anwendung mit Xcode und stellen Sie die App dem iOS-Simulator bereit.
Es sollte eine Liste der Adventures von der WKND-Website in der Anwendung angezeigt werden. Bei Auswahl eines Adventures werden die zugehörigen Details geöffnet. Führen Sie in der Liste der Adventures einen Pull aus, um die Daten aus AEM zu aktualisieren.
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.
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:
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
}
}
}
}
}
wknd/adventure-by-slug
gibt ein einzelnes Adventure durch slug
(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
}
}
}
}
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(..)
oder getAdventureBySlug(..)
), 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/Models.swift
definierten Strukturen/Klassen decodiert, die den von AEM Headless zurückgegebenen JSON-Objekten zugeordnet sind. /// # 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
}
...
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.
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 über aem.getAdventures()
abzurufen. Das gemeinsame aem
-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 von AdventureListItemView
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 der slug
-Parameter basierend auf der ausgewählten Listenzeile übergeben wird.
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 der Entwicklung mit dem AEM-SDK _dynamicUrl
gibt null zurück, sodass für die Entwicklungs-Fallback zum Bild _path
-Feld.
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(..)
in Aem
legt HTTP-Autorisierungs-Header für die Bild-HTTP-Anfrage fest, basierend auf der Konfiguration der iOS-Anwendungskonfiguration.
/// 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")
}
/// 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")
}
Ein ähnlicher Ansatz kann mit der SwiftUI-nativen AsyncImage-Ansicht verwendet werden. AsyncImage
wird ab iOS 15.0 unterstützt.