iOS 앱

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

예제 애플리케이션은 AEM(Adobe Experience Manager)의 Headless 기능을 살펴볼 수 있는 좋은 방법입니다. 이 iOS 애플리케이션은 지속 쿼리를 사용하여 AEM의 GraphQL API를 사용하여 콘텐츠를 쿼리하는 방법을 보여 줍니다.

AEM Headless가 포함된 iOS SwiftUI 앱

GitHub에서 소스 코드 보기

사전 요구 사항 prerequisites

다음 도구를 로컬에 설치해야 합니다.

AEM 요구 사항

iOS 애플리케이션은 다음 AEM 배포 옵션과 함께 작동합니다. 모든 배포를 사용하려면 WKND 사이트 v3.0.0+을(를) 설치해야 합니다.

iOS 응용 프로그램은 AEM Publish 환경에 연결하도록 디자인되었지만, iOS 응용 프로그램의 구성에서 인증을 제공하는 경우 AEM 작성자의 콘텐츠를 소싱할 수 있습니다.

사용 방법

  1. adobe/aem-guides-wknd-graphql 리포지토리 복제:

    code language-shell
    $ git clone git@github.com:adobe/aem-guides-wknd-graphql.git
    
  2. Xcode을(를) 열고 ios-app 폴더를 엽니다.

  3. Config.xcconfig 파일을 수정하고 대상 AEM Publish 서비스와 일치하도록 AEM_SCHEMEAEM_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_USERNAMEAEM_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
    
  4. Xcode를 사용하여 애플리케이션을 빌드하고 앱을 iOS 시뮬레이터에 배포합니다.

  5. WKND 사이트의 모험 목록이 애플리케이션에 표시되어야 합니다. 모험을 선택하면 어드벤처 세부 정보가 열립니다. 모험 목록 보기에서 를 가져와서 AEM에서 데이터를 새로 고칩니다.

코드

다음은 iOS 애플리케이션이 빌드되는 방법, GraphQL 지속 쿼리를 사용하여 콘텐츠를 검색하기 위해 AEM Headless에 연결하는 방법 및 해당 데이터가 표시되는 방법에 대한 요약입니다. 전체 코드는 GitHub에서 찾을 수 있습니다.

지속 쿼리

AEM Headless 우수 사례에 따라 iOS 애플리케이션은 AEM GraphQL 지속 쿼리를 사용하여 어드벤처 데이터를 쿼리합니다. 이 응용 프로그램에서는 두 개의 지속 쿼리를 사용합니다.

  • 속성 집합이 포함된 AEM의 모든 모험을 반환하는 wknd/adventures-all 지속 쿼리입니다. 이 지속 쿼리는 초기 보기의 모험 목록을 구동합니다.
# 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
        }
      }
    }
  }
}
  • 전체 속성 집합이 있는 slug(모험을 고유하게 식별하는 사용자 지정 속성)이 단일 모험을 반환하는 wknd/adventure-by-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 Headless와의 모든 상호 작용에 사용되는 Aem 클래스를 인스턴스화합니다. 패턴은 다음과 같습니다.

  1. 각 지속 쿼리에는 해당 공개 함수(예: getAdventures(..) 또는 getAdventureBySlug(..)) 모험 데이터를 가져오기 위해 iOS 응용 프로그램의 보기를 호출합니다.

  2. 공용 함수는 AEM Headless에 비동기 HTTP GET 요청을 호출하고 JSON 데이터를 반환하는 개인 함수 makeRequest(..)을(를) 호출합니다.

  3. 각 퍼블릭 펀드는 JSON 데이터를 디코딩한 다음, Adventure 데이터를 보기에 반환하기 전에 필요한 확인 또는 변환을 수행합니다.

    • AEM의 GraphQL JSON 데이터는 AEM Headless가 반환한 JSON 개체에 매핑되는 AEM/Models.swift에 정의된 구조/클래스를 사용하여 디코딩됩니다.
    /// # 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 응답에 매핑되는 디코딩 가능 Swift 구조 및 클래스를 정의합니다.

보기

SwiftUI는 애플리케이션의 다양한 보기에 사용됩니다. Apple은 SwiftUI를 사용하여 목록 작성 및 탐색을 위한 시작 자습서를 제공합니다.

  • WKNDAdventuresApp.swift

    응용 프로그램의 항목이며 aem.getAdventures()을(를) 통해 모든 모험 데이터를 가져오는 데 .onAppear 이벤트 처리기를 사용하는 AdventureListView이(가) 포함되어 있습니다. 공유 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_SCHEMEAEM_HOST 접두사를 사용하여 정규화된 URL을 만듭니다. AE SDK에 대해 개발하는 경우 _dynamicUrl이(가) null을 반환하므로 개발 대체 항목이 이미지의 _path 필드로 설정됩니다.

인증이 필요한 AEM의 보호된 리소스에 연결하는 경우 자격 증명을 이미지 요청에도 추가해야 합니다.

SDWebImageSwiftUISDWebImage은(는) AdventureListItemViewAdventureDetailView 보기에서 어드벤처 이미지를 채우는 AEM의 원격 이미지를 로드하는 데 사용됩니다.

aem 클래스(AEM/Aem.swift의)는 두 가지 방법으로 AEM 이미지의 사용을 용이하게 합니다.

  1. 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
    
  2. Aemconvenience 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 이상에서 지원됩니다.

추가 리소스

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