使用無頭 (headless) Oracle Content Management 在 Swift 打造部落格
簡介
使用 Swift 和 SwiftUI 的 iOS 開發環境是一項功能強大的工具,可用來建置使用來自 Oracle Content Management 內容的應用程式。有了正確的內容模型,您可以快速建立構成典型部落格 App 的使用者介面。
本教學課程將運用 Oracle Content Management 作為無頭 (headless) CMS 及其軟體開發套件 (SDK) 進行內容傳遞,在 Swift 中建置簡單的 iOS 部落格應用系統。此 iOS 範例位於 GitHub 。
教學課程包含三個步驟:
必要條件
繼續本教學課程之前,建議您先閱讀下列資訊:
若要依循此教學課程,您需要:
- Oracle Content Management 訂閱
- 具有「內容管理員」角色的 Oracle Content Management 帳戶
- Xcode 版本 14.2 或更新版本的 Mac 電腦
我們正在打造什麼
我們的部落格應用程式包含三個不同的頁面,可讓訪客瀏覽編排到主題的部落格文章。

第一頁是首頁,包含品牌建立 (公司名稱和標誌)、部分連結,以及部落格主題清單。
第二頁是文章清單頁面,顯示屬於該主題之每個部落格文章的預覽。
最後,文章頁面會呈現最終部落格文章,包括部落格作者的相關資訊。
若要繼續,您必須擁有 Oracle Content Management 的有效訂閱,並且以「內容管理員」角色登入。
步驟 1:準備 Oracle Content Management
如果您尚未擁有 Oracle Content Management 執行處理,請參閱快速入門,瞭解如何註冊 Oracle Cloud、佈建 Oracle Content Management 執行處理,以及將 Oracle Content Management 設定為無頭 CMS。
對於此教學課程,您需要以兩種方式建立內容模型。有可下載的資產套件可供使用,將內容類型與相關內容填入您的空白儲存庫,或者您可以建立自己的內容模型與內容。
若要準備 Oracle Content Management:
- 建立通道和資產儲存區域。
- 使用下列兩種方法之一來建立內容模型:
- 方法 1:匯入 Oracle Content Management 範例資產套件
- 方法 2:建立自己的內容模型
建立通道和資產儲存區域
您必須先在 Oracle Content Management 中建立通道和資產儲存區域,才能發布內容。
在 Oracle Content Management 中建立通道和資產儲存區域:
以管理員身分登入 Oracle Content Management Web 介面。
從左側導覽功能表中選擇內容,然後從頁面標頭的選取清單中選擇發布管道。

在右上角,按一下建立 (Create) 以建立新通道。請為此教學課程命名通道 'OCEGettingStartedChannel',並讓存取保持公用。按一下儲存以建立管道。

在左側導覽功能表中選擇內容,然後從頁面表頭的選取清單中選擇儲存庫。

在右上角,按一下建立以建立新的資產儲存庫。請為此教學課程命名資產儲存區域 'OCEGettingStartedRepository'。

在發布通道欄位中,選取 OCEGettingStartedChannel 通道以向 Oracle Content Management 表示 OCEGettingStartedRepository 儲存區域中的內容可發布至 OCEGettingStartedChannel 通道。完成後按一下儲存。

建立內容模型
下一步是建立內容模型。您可以使用兩個方法中的任一個:
- 方法 1:匯入 Oracle Content Management 範例資產套件
- 方法 2:建立自己的內容模型
匯入 Oracle Content Management 範例資產套件
您可以下載預先設定的 Oracle Content Management 範例資產套件,其中包含此教學課程的所有必要內容類型和資產。您也可以選擇建立自己的內容模型,而非下載範例資產套件。
您可以從 Oracle Content Management 範例資產套件上傳本教學課程中使用的內容複本。這可讓您對內容類型進行實驗並修改內容。若要匯入 Oracle Content Management 範例資產套件,請下載資產套件封存 OCESamplesAssetPack.zip,並將其擷取至您選擇的目錄:
從 Oracle Content Management 下載頁面下載 Oracle Content Management 範例資產套件 (OCESamplesAssetPack.zip)。將下載的壓縮檔解壓縮至電腦上的位置。擷取之後,此位置將會包含一個名為 OCEGettingStarted_data.zip 的檔案。
以管理員身分登入 Oracle Content Management Web 介面。
在左側導覽功能表中選擇內容,然後從頁面表頭的選取清單中選擇儲存庫。現在選取 OCEGettingStartedRepository ,然後按一下頂端動作列中的匯入內容按鈕。

將本機電腦中的 OCEGettingStarted_data.zip 上傳至文件資料夾。

上傳後,請選取 OCEGettingStarted_data.zip ,然後按一下確定,將內容匯入資產儲存庫。

順利匯入內容之後,請瀏覽至資產頁面,然後開啟 OCEGettingStartedRepository 儲存庫。您會看到所有相關的影像和內容項目現在都已新增至資產儲存庫。

按一下左上方的全選,然後按一下發布,將所有匯入的資產新增至稍早建立的發布通道 OCEGettingStartedChannel。

發布之前,您需要驗證所有資產。首先,將 OCEGettingStartedChannel 新增為選取的通道,然後按一下 [ 驗證 ] 按鈕。

資產經過驗證之後,您可以按一下右上角的發布按鈕,將所有資產發布至選取的管道。

完成之後,您可以在資產頁面上查看所有已發布的資產。(您可以透過資產名稱上方的圖示來說明。)

匯入 Oracle Content Management 範例資產套件之後,您可以開始在 Xcode 中建立部落格。
建立您自己的內容模型
您也可以建立自己的內容模型,而非匯入 Oracle Content Management 範例資產套件。
本教學課程使用名為 'OCEGettingStartedHomePage' 的內容類型來建立部落格的首頁。此首頁包含品牌 (公司名稱和標誌)、一些連結 URL,以及應包含在頁面中的部落格主題清單。

若要建立內容模型的內容類型:
- 以管理員身分登入 Oracle Content Management Web 介面。
- 從左側導覽功能表中選擇內容,然後從頁面標頭的選取清單中選擇資產類型。
- 按一下右上角的建立 (Create) 。
- 選擇建立內容類型 (不是數位資產類型)。針對所有必要的內容類型重複此步驟。

我們會建立四種內容類型,每種類型都有其專屬的欄位集合:
- OCEGettingStartedHomePage
- OCEGettingStartedTopic
- OCEGettingStartedAuthor
- OCEGettingStartedArticle
第一個內容類型 OCEGettingStartedHomePage 應具有下列欄位:
| 顯示名稱 | 欄位類型 | 必要 | 機器名稱 |
|---|---|---|---|
| 公司名稱 | 單值文字欄位 | X | company_name |
| 公司商標 | 單值文字欄位 | X | company_logo |
| 主題 | 多重值參考欄位 | X | topics - 主題 |
| 聯絡人 URL | 單值文字欄位 | X | contact_url |
| 關於 URL | 單值文字欄位 | X | about_url |
這是您的 OCEGettingStartedHomePage 內容類型定義外觀:

第二個內容類型 OCEGettingStartedTopic 應具有下列欄位:
| 顯示名稱 | 欄位類型 | 必要 | 機器名稱 |
|---|---|---|---|
| 縮圖 | 單一值影像欄位 | X | 縮圖 |
這是 OCEGettingStartedTopic 內容類型的外觀:

第三個內容類型 OCEGettingStartedAuthor 應具有下列欄位:
| 顯示名稱 | 欄位類型 | 必要 | 機器名稱 |
|---|---|---|---|
| 大頭貼 | 單一值影像欄位 | X | 大頭貼 |
這是 OCEGettingStartedAuthor 內容類型的外觀:

第四種與最終內容類型 OCEGettingStartedArticle 應具有下列欄位:
| 顯示名稱 | 欄位類型 | 必要 | 機器名稱 |
|---|---|---|---|
| 發布日期 | 單一值日期欄位 | X | published_name |
| 作者 | 單一值參考欄位 | X | 撰寫 |
| 影像 | 單一值影像欄位 | X | 影像 |
| 圖像標題 | 單值文字欄位 | X | image_caption |
| 文章內容 | 單一值大型文字欄位 | X | article_content |
| 主題 | 單一值參考欄位 | X | topic - 主題 |
這是 OCEGettingStartedArticle 內容類型的外觀:

建立內容類型之後,您可以將這些內容類型新增至先前建立的儲存庫,OCEGettingStartedRepository:
- 以管理員身分登入 Oracle Content Management Web 介面。
- 瀏覽至 OCEGettingStartedRepository 。
- 編輯儲存區域,然後在資產類型底下指定這四個新建立的內容類型。按一下儲存按鈕以儲存變更。

將內容類型新增至儲存庫後,您可以在資產頁面上開啟 OCEGettingStartedRepository 儲存庫,然後開始為所有內容類型建立內容項目。

步驟 2:以 Xcode 建立部落格應用程式
若要在 iOS 應用程式中使用 Oracle Content Management 內容,您可以使用 iOS 部落格範例,此範例可作為 GitHub 上的開源儲存庫。
注意:請記住,使用 iOS 範例是選擇性的,在此教學課程中使用它可讓您快速開始。您也可以建置您自己的 iOS 應用程式。
若要在 Swift 建置部落格:
複製範例儲存區域
iOS 部落格範例可作為 GitHub 上的開放原始碼儲存庫。
您必須先將範例從 GitHub 複製到本機電腦:
git clone https://github.com/oracle-samples/oce-ios-blog-sample.git
複製範例之後,請開啟 Xcode 專案檔 BlogDemo.xcodeproj。
以 Xcode 開啟範例專案時,它會自動提取 content-management-swift 的相依性,Swift 套裝程式會實行 Oracle Content Delivery SDK。
此應用程式沒有其他協力廠商相依性,因此不需要進行其他手動安裝。不過,在執行應用程式之前,還需要設定其他組態。
設定 iOS 應用程式
在此 iOS 部落格範例中,您需要設定一些資訊,讓 Oracle Content Management Content SDK 能夠使用正確的通道記號來鎖定正確的執行處理 URL。每次您的應用程式要求來自 Oracle Content Management 執行處理的資料時都會使用這些值。
開啟 credentials.json 檔案並變更這兩個索引鍵 - 值組,以反映您的執行處理 URL 和與發布通道關聯的通道記號。本教學課程的頻道為 OCEGettingStartedChannel。
{
"url": "https://samples.mycontentdemo.com",
"channelToken": "47c9fb78774d4485bc7090bf7b955632"
}使用 Content SDK 擷取內容
Oracle Content Management 提供由 OracleContentCore 和 OracleContentDelivery 程式庫組成的 Swift 套裝軟體 (content-management-swift),可協助您尋找與使用應用程式中的內容。套裝軟體是在 GitHub 上代管。
進一步瞭解 Oracle Content Management 文件庫中 Swift 的 Content SDK:
您可以利用這些 Content SDK 程式庫擷取內容,讓我們可以在 iOS 應用程式中轉譯。
將應用程式上線
若要要求資料,您必須提供一些必要資訊給程式庫,這稱為將應用程式上線。您可以指定特定的資訊片段,以便正確地格式化要求並指向正確的執行處理 URL。
在這個示範中,當應用程式首次啟動時,Onboarding.urlProvider 會被指派為您自己的類別 MyURLProvider 的執行處理。這會建立內容庫針對每個提出之要求所使用的執行處理 URL 和通道記號。
指定 URL 提供者並非絕對必要 (因為 URL 和記號資訊可以根據每個要求加以指定),但指派這些提供者會減少每個呼叫網站所需的代碼數量。
@main
struct BlogDemo: App {
init() {
// ...
// The sample code expects the URL and channel token to be provided by ``OracleContentCore.Onboarding``
// Assign your ``OracleContentCore.URLProvider`` implementation to the ``OracleContentCore.Onboarding.urlProvider`` property
Onboarding.urlProvider = MyURLProvider()
// ...
}
}MyURLProvider 實行會從 credentials.json 讀取資料,以取得 URL 和通道記號。
{
"url": "https://samples.mycontentdemo.com",
"channelToken": "47c9fb78774d4485bc7090bf7b955632"
}每次 Oracle 內容庫需要建立要求時,都會擷取下列特性:
/// This function provides the URL to be used for each OracleContentCore request
///
/// Services which implement ``OracleContentCore.ImplementsOverrides`` may provide a different URL and
/// authorization headers (if required) on a call-by-call basis
public var url: () -> URL? = {
return URL(string: MyURLProvider.credentials.url)
}程式庫每次需要建立要求時,也會擷取傳遞權杖:
/// This function provides the delivery channel token to be used for each OracleContentCore request
///
/// Services which implement ``OracleContentCore.ImplementsChannelToken`` may override this value
/// on a call-by-call basis
public var deliveryChannelToken: () -> String? = {
return MyURLProvider.credentials.channelToken
}此外,指派給 Onboarding.logger 可讓您定義自己的記錄日誌實行。基於這個示範,MyLogger 實行 只包含 "printing" 到主控台。在生產環境中,記錄器可以使用通用記錄日誌、核心資料或您選擇的任何技術。
@main
struct BlogDemo: App {
init() {
// ...
Onboarding.logger = MyLogger()
// ...
}
}使用 Oracle Content Library 要求資料
備註:您可以在 BlogNetworking.swift 中找到此示範的所有網路要求代碼。
我們現在可以運用 Content SDK 擷取內容,讓我們在 iOS 應用程式中轉譯。
BlogNetworking.swift 檔案包含用來取得應用程式資料的所有程式碼。與每個頁面相關聯的模型物件會呼叫各種方法,以擷取 Oracle Content Management 資料。
首頁資料
我們應用程式的首頁是 BlogDemoMain.swift,一個含有相關模型物件的 SwiftUI 檔案。模型物件可重新用於維護頁面狀態,並發出啟動資料擷取處理所需的函數呼叫。當收到資料時,頁面狀態會變更,而 SwiftUI 將會相應地重新整理 UI。
一開始,主頁面會要求兩項不同的資料:
- 首先,我們查詢代表部落格首頁的內容項目。
- 然後下載內容項目所參考的標誌。
開啟 BlogNetworking.swift 並尋找執行初始要求的 fetchHomePage() 函數。
/// Retrieve the content item which represents the home page of the blog
/// - returns: Asset
public func fetchHomePage() async throws -> Asset {
let typeNode = QueryNode.equal(field: "type", value: "OCEGettingStartedHomePage")
let nameNode = QueryNode.equal(field: "name", value: "HomePage")
let q = QueryBuilder(node: typeNode).and(nameNode)
let result = try await DeliveryAPI
.listAssets()
.query(q)
.fields(.all)
.limit(1)
.fetchNextAsync()
.items
.first
guard let foundResult = result else {
throw BlogNetworkingError.homePageNotFound
}
return foundResult
}此函數會使用 Swift 並行來要求資料。請注意,所有要求物件 (或服務) 皆已命名空間至 DeliveryAPI。在此範例中,我們的要求物件為 listAssets()。
依照要求物件是一系列的產生器元件,讓我們能夠微調要求。我們已在此處指定一些查詢詳細資訊,要求擷取所有欄位,並只將回應資料限制在單一物件。
我們對 Oracle Content Management 的要求是使用呼叫動詞 fetchNextAsync() 來提交。
注意: 由於我們的要求物件以 "list" 為開頭,因此呼叫動詞將以 "fetchNext" 為開頭。
「清單」要求會傳回資料,指出傳回多少記錄、是否有更多資料,以及結果集合 (在「項目」特性中)。
我們的函數會從 "items" 特性抓取第一個值並傳回該值。這是代表我們部落格的資產,包含標誌 ID、公司名稱、關於和聯絡人 URL,以及主題清單。
只要備妥標誌 ID,我們就可以發出第二個下載標誌要求:
/// Download the logo for display on the home page
/// - parameter logoId: The identifier of the logo to download
/// - returns: BlogImageState
public func fetchLogo(logoId: String?) async throws -> BlogImageState {
do {
guard let logoID = logoId else {
throw BlogNetworkingError.missingLogoId
}
let result = try await DeliveryAPI
.downloadNative(identifier: logoID)
.downloadAsync(progress: nil)
guard let image = UIImage(contentsOfFile: result.result.path) else {
throw OracleContentError.couldNotCreateImageFromURL(URL(string: result.result.path))
}
return .image(image)
} catch {
return .error(error)
}
}我們的要求和調用在此功能中比較簡單。我們使用 downloadNative(identifier: logoID) 建立要求物件,然後使用呼叫動詞 downloadAsync 提交該物件。
下載物件後,我們會將資料轉換成 UIImage,並將其以 BlogImageState 列舉形式傳回影像本身作為關聯值。
注意:此範例使用
BlogImageState列舉,因為我們允許我們代表影像的各種狀態,而不需要使用選擇性值。這樣便於在 SwiftUI 中攝取。
主題資料
當我們的模型要求首頁資料時,它會執行如下所示的呼叫:
self.home = try await self.networking.fetchHomePage() 成功擷取首頁後,我們需要尋找代表我們部落格主題的資產價值集合。為此,我們要求名為「主題」的欄位,其類型為資產陣列。
self.topics = try self.home.customField("topics") as [Asset]BlogDemoMain.swift 會重複此主題集合,並表示每個主題為方格檢視中的元素。該 UI 表示法定義於 Topic.swift 及其關聯的模型物件 TopicModel.swift 中。
為了顯示個別主題的相關資訊,我們必須執行兩個任務:
- 我們需要擷取每個主題的詳細資訊。
- 我們必須下載每個主題所參考的影像。
在 TopicModel.swift 中,我們撥打網路代碼以取得完整資產:
let fullAsset = try await self.networking.readAsset(assetId: self.topic.identifier)取得主題的詳細資訊是一個簡單程序。請使用指定的識別碼建構讀取要求,然後擷取資料。
/// Obtain detailed information about an Asset
/// - parameter assetId: The identifier of the asset to read
/// - returns: Asset
public func readAsset(assetId: String) async throws -> Asset {
let result = try await DeliveryAPI
.readAsset(assetId: assetId)
.fetchAsync()
return result
}注意:所有讀取要求都會傳回單一物件,並使用以 "fetch" 開頭的呼叫動詞來送出。
我們擷取了關於主題的詳細資訊之後,就必須擷取定義縮圖的資料,然後下載該縮圖。
在 TopicModel.swift 中,我們會要求名稱為 "thumbnail" 的自訂欄位來取得縮圖資訊。由於自訂欄位可以是許多不同類型之一,因此必須明確指定此欄位為資產類型。順利找到資產之後,我們就可以抓取其識別碼。
imageIdentifier = (try fullAsset.customField("thumbnail") as Asset).identifier使用可用的識別碼,TopicModel.swift 現在可以要求網路代碼擷取縮圖影像的媒體轉譯。
let imageState = await self.networking.downloadMediumRendition(identifier: imageIdentifier)
return imageState取得影像的網路代碼會建立一個 "downloadRendition" 要求物件,以 jpg 格式擷取名稱為 'Medium' 的轉譯。用來提交要求的呼叫動詞是 "downloadAsync"。
下載物件後,我們會將資料轉換成 UIImage,並將其以 BlogImageState 列舉形式傳回影像本身作為關聯值。
/// Downloads the "Medium" rendition of an asset and returns the value as a `BlogImageState`
/// Note that any error while downloading the image will result in a placeholder image
public func downloadMediumRendition(identifier: String) async -> BlogImageState {
do {
let result = try await DeliveryAPI
.downloadRendition(identifier: identifier,
renditionName: "Medium",
format: "jpg")
.downloadAsync(progress: nil)
guard let uiImage = UIImage(contentsOfFile: result.result.path()) else {
throw OracleContentError.couldNotCreateImageFromURL(result.result)
}
return .image(uiImage)
} catch {
return .image(UIImage(systemName: "questionmark.circle")!)
}
}現在,我們有詳細的主題資訊和縮圖影像,SwiftUI 可以建構主題的視覺呈現。

文章列表資料
點選主題,將使用者導覽至包含指定給所選主題的文章清單的新頁面。
建立文章頁面與用來建立主題清單的程序非常相似。針對主題識別碼,我們需要:
- 擷取該主題的文章清單,以及
- 針對每篇文章,擷取其「影像」欄位中所含資產的縮圖轉譯。
擷取資產清單時,我們的要求物件為 .listAssets()。
缺少任何其他建構器元件,這會從 credentials.json 中指定的發布通道擷取所有已發布的資產。不過,我們希望只擷取類型為 OCEGettingStartedArticle 且主題特性符合傳入主題識別碼的資產。
/// Obtain the collection of articles for a given topic. Limited to a maximum of 50 articles for demo purposes.
/// - parameter topic: Asset
/// - returns: [Asset] representing the articles for the specified topic
public func fetchArticles(for topic: Asset) async throws -> [Asset] {
let typeNode = QueryNode.equal(field: "type", value: "OCEGettingStartedArticle")
let fieldsTopicNode = QueryNode.equal(field: "fields.topic", value: topic.identifier)
let fullQuery = QueryBuilder(node: typeNode).and(fieldsTopicNode)
let result = try await DeliveryAPI
.listAssets()
.query(fullQuery)
.order(.field("fields.published_date", .desc))
.limit(50)
.fetchNextAsync()
return result.items
}針對每一個擷取的文章,我們需要決定要下載哪些影像。我們會取得「影像」欄位中參照的資產 ID,並用來提交要求以下載其縮圖。
let identifier = (try article.customField("image") as Asset).identifier
let image = await self.networking.downloadThumbnail(identifier: identifier, fileGroup: article.fileGroup)要求下載的網路代碼如下所示:
/// Downloads the thumbnail rendition of an asset and returns the values as a ``BlogImageState``
/// Note that any error while downloading the image will result in a placeholder image
/// - parameter identifier: The identifier of the asset
/// - parameter fileGroup: The file group of the asset - used to differentiate thumbnails for digital assets, videos and "advanced videos"
public func downloadThumbnail(identifier: String, fileGroup: String) async -> BlogImageState {
do {
let result = try await DeliveryAPI
.downloadThumbnail(identifier: identifier, fileGroup: fileGroup)
.downloadAsync(progress: nil)
guard let uiImage = UIImage(contentsOfFile: result.result.path()) else {
throw OracleContentError.couldNotCreateImageFromURL(result.result)
}
return .image(uiImage)
} catch {
Onboarding.logError(error.localizedDescription)
return .image(UIImage(systemName: "questionmark.circle")!)
}
}我們現在可以預覽每篇文章 :

文章資料
點選文章預覽會導致範例應用程式的最終頁面顯示,亦即文章本身。之前必須執行幾個資料要求:
- 根據所選文章的識別碼,我們需要擷取其詳細資訊。
- 下載作者的大頭照。
- 下載要顯示的文章影像。
@MainActor
func fetchArticle() async throws {
self.article = try await self.networking.readArticle(assetId: article.identifier)
let author: Asset = try self.article.customField("author")
let authorAvatar: Asset = try author.customField("avatar")
Task {
self.avatar = await self.networking.downloadNative(identifier: authorAvatar.identifier)
}
Task {
let hero: Asset = try self.article.customField("image_16x9")
self.heroImage = await self.networking.downloadNative(identifier: hero.identifier)
}
}閱讀資產的詳細資訊,過程相當簡單:
/// Obtain detailed information about an Asset
/// - parameter assetId: The identifier of the asset to read
/// - returns: Asset
public func readAsset(assetId: String) async throws -> Asset {
let result = try await DeliveryAPI
.readAsset(assetId: assetId)
.fetchAsync()
return result
}下載大頭貼和文章影像皆遵循用來取得其他影像的一般樣式:
/// Downloads the native rendition of an asset and returns the values as a ``BlogImageState``
/// Note that any error while downloading the image will result in a placeholder image
public func downloadNative(identifier: String) async -> BlogImageState {
do {
let result = try await DeliveryAPI
.downloadNative(identifier: identifier)
.downloadAsync(progress: nil)
guard let uiImage = UIImage(contentsOfFile: result.result.path()) else {
throw OracleContentError.couldNotCreateImageFromURL(result.result)
}
return .image(uiImage)
} catch {
Onboarding.logError(error.localizedDescription)
return .image(UIImage(systemName: "questionmark.circle")!)
}
}結果是完整格式的部落格文章:

步驟 3:執行您的應用程式
應用程式完成後,您可以在模擬器中執行該應用程式,或將其部署至任何執行 iOS 或 iPadOS 16.0 或更新版本的 iPhone 或 iPad。
結論
在本教學課程中,我們建立了 iOS 部落格應用程式網站,您可以在 GitHub 找到此網站。此應用程式使用 Oracle Content Management 作為無頭 CMS。使用部落格教學課程的發布內容通道設定及設定 Oracle Content Management 之後,我們會執行應用程式來擷取必要的內容。
如需 Swift 的詳細資訊,請前往 Swift 網站。
瞭解文件中重要的 Oracle Content Management 概念。
您可以在 Oracle Help Center 的 Oracle Content Management 範例頁面上找到更多範例。