iOS アプリ
[AEM Headless as a Cloud Service]{class="badge informative"}
サンプルアプリケーションは、Adobe Experience Manager(AEM)のヘッドレス機能を調べるうえで役に立ちます。 この iOS アプリケーションでは、永続クエリを使用して AEM の GraphQL API でコンテンツに対してクエリを実行する方法を示します。
前提条件 prerequisites
次のツールをローカルにインストールする必要があります。
AEM の要件
iOS アプリケーションは、次の AEM デプロイメントオプションと連携します。すべてのデプロイメントに WKND Site v3.0.0 以降をインストールする必要があります。
- AEM as a Cloud Service
- AEM Cloud Service SDK を使用したローカル設定
iOS アプリケーションは AEM パブリッシュ 環境に接続するように設計されていますが、iOS アプリケーションの設定で認証が提供されている場合、AEM オーサーからコンテンツを取得できます。
使用方法
-
adobe/aem-guides-wknd-graphql
リポジトリのクローンを作成します。code language-shell $ git clone git@github.com:adobe/aem-guides-wknd-graphql.git
-
Xcode、フォルダー
ios-app
の順に開く -
Config.xcconfig
ファイルを変更し、ターゲットの AEM パブリッシュサービスに一致するようにAEM_SCHEME
とAEM_HOST
を更新します。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
AEM オーサーに接続する場合は、
AEM_AUTH_TYPE
およびサポートする認証プロパティをConfig.xcconfig
に追加します。基本認証
AEM_USERNAME
とAEM_PASSWORD
は、WKND GraphQL コンテンツにアクセスできるローカル AEM ユーザーを認証します。code language-plain AEM_AUTH_TYPE = basic AEM_USERNAME = admin AEM_PASSWORD = admin
トークン認証
AEM_TOKEN
はアクセストークンであり、WKND GraphQL コンテンツへのアクセス権を持つ AEM ユーザーを認証します。code language-plain AEM_AUTH_TYPE = token AEM_TOKEN = abcd...0123
-
Xcode を使用してアプリケーションを作成し、アプリケーションを iOS シミュレーターにデプロイします。
-
WKND サイトからのアドベンチャーのリストがアプリケーションに表示されます。アドベンチャーを選択すると、アドベンチャーの詳細が開きます。アドベンチャーリストビューで、AEM からのデータを取り込んで更新します。
コード
以下は、iOS アプリケーションの構築方法、AEM ヘッドレスに接続して GraphQL 永続クエリを使用してコンテンツを取得する方法、およびそのデータを表示する方法の概要です。完全なコードは GitHub にあります。
永続クエリ
AEM ヘッドレスのベストプラクティスに従って、iOS アプリケーションは AEM GraphQL 永続クエリを使用してアドベンチャーデータをクエリします。アプリケーションでは、次の 2 つの永続クエリを使用します。
wknd/adventures-all
永続クエリ。AEM のすべてのアドベンチャーを要約されたプロパティセットとともに返します。この永続クエリは、初期ビューのアドベンチャーリストを制御します。
# 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
永続クエリ。slug
(アドベンチャーを一意に識別するカスタムプロパティ)によって、完全なプロパティセットを含む単一のアドベンチャーを返します。この永続クエリで、アドベンチャーの詳細ビューが強化されます。
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
}
}
}
}
GraphQL 永続クエリの実行
AEM の永続クエリは HTTP GET で実行されるので、Apollo などの HTTP POST を使用する一般的な GraphQL ライブラリは使用できません。代わりに、AEM に対する永続クエリ HTTP GET リクエストを実行するカスタムクラスを作成します。
AEM/Aem.swift
は AEMヘッドレスとのすべてのやり取りに使用される Aem
クラスをインスタンス化します。パターンは次のとおりです。
-
各永続クエリには、対応するパブリック関数(例:
getAdventures(..)
またはgetAdventureBySlug(..)
)があり、iOS アプリケーションのビューが呼び出されて、アドベンチャーデータを取得します。 -
パブリック関数は、AEM ヘッドレスへの非同期 HTTP GET リクエストを呼び出すプライベート関数
makeRequest(..)
を呼び出し、JSON データを返します。 -
次に、各パブリック関数は JSON データをデコードし、必要なチェックや変換を行ってから、アドベンチャーデータをビューに返します。
- AEM の GraphQL JSON データは、
AEM/Models.swift
で定義された構造体/クラスを使用してデコードされます。これは、AEM ヘッドレスから返された JSON オブジェクトにマップされます。
- AEM の GraphQL JSON データは、
/// # 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 応答データモデル
iOS では、JSON オブジェクトを型指定されたデータモデルにマッピングすることが好まれています。
src/AEM/Models.swift
は、AEM の JSON 応答によって返される AEM JSON 応答にマップされる decodable Swift 構造体とクラスを定義します。
ビュー
SwiftUI は、アプリケーションの様々な表示で使用されます。 Apple は、SwiftUI を使用してリストとナビゲーションを構築するための入門チュートリアルを提供しています。
-
WKNDAdventuresApp.swift
アプリケーションのエントリには
AdventureListView
が含まれ、その.onAppear
イベントハンドラーはaem.getAdventures()
を介してすべてのアドベンチャーデータを取得するために使用されます。共有aem
オブジェクトはここで初期化され、他のビューには EnvironmentObject として公開されます。 -
Views/AdventureListView.swift
(
aem.getAdventures()
からのデータに基づく)アドベンチャーのリストを表示し、AdventureListItemView
を使用している各アドベンチャーのリスト項目を表示します。 -
Views/AdventureListItemView.swift
アドベンチャーリストの各項目を表示します(
Views/AdventureListView.swift
)。 -
Views/AdventureDetailView.swift
タイトル、説明、価格、アクティビティタイプ、プライマリ画像など、アドベンチャーの詳細を表示します。 このビューは、
aem.getAdventureBySlug(slug: slug)
を使用して完全なアドベンチャーの詳細のクエリを AEM に対して実行します。ここで、slug
パラメーターは選択リストの行に基づいて渡されます。
リモート画像
アドベンチャーコンテンツフラグメントで参照される画像は、AEM が提供します。 この iOS アプリは、GraphQL 応答のパス _dynamicUrl
フィールドを使用し、プレフィックス AEM_SCHEME
と AEM_HOST
を付けて完全修飾 URL を作成します。AEM SDK に対応した開発の場合、_dynamicUrl
は null を返すので、画像の _path
フィールドにフォールバックします。
認証が必要な AEM 上の保護されたリソースに接続する場合は、資格情報も画像リクエストに追加する必要があります。
SDWebImageSwiftUI および SDWebImage は、AdventureListItemView
表示と AdventureDetailView
表示に アドベンチャー画像を取り込む AEM からリモート画像を読み込むために使用されます。
aem
クラス(AEM/Aem.swift
)は、次の 2 つの方法で AEM 画像の使用を容易にします。
-
aem.imageUrl(path: String)
はビューで使用され、AEM のスキームとホストを画像のパスに追加して、完全修飾 URL を作成します。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
-
Aem
のconvenience init(..)
は、iOS アプリケーションの設定に基づいて、画像 HTTP リクエストに HTTP 認証ヘッダーを設定します。- 基本認証 が設定されている場合、すべての画像リクエストで基本認証が行われます。
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") }
- トークン認証 が設定されている場合、すべての画像リクエストでトークン認証が行われます。
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") }
- 認証なし が設定されている場合、画像リクエストで認証は行われません。
SwiftUI ネイティブの AsyncImage でも同様のアプローチを使用できます。AsyncImage
は iOS 15.0 以降でサポートされています。