SwiftUI 教學:運用不同 UI 元件 輕鬆建立一個電影預告片 App


本篇原文(標題:Building Movie Trailer App Using SwiftUI)刊登於作者 Medium,由 Shankar Madeshvaran 所著,並授權翻譯及轉載。

Apple 在 WWDC19 介紹了最新的開發框架,其中之一就是 SwiftUI 以及 Combine。如果你還沒有知道這個消息,簡單來說,SwiftUI 是一種新的方法,讓我們可以藉由宣告方式來創建 UI;而 Combine 是與它一起使用的,Combine 提供了宣告式 Swift API,以處理像是 UI 或是 Network 事件的值。

如果你是剛接觸 SwiftUI,我推薦你可以閱讀這篇文章來瞭解它的基礎概念。

譯者備註:如果你不熟悉 Combine 和 SwiftUI,也可以參考我們之前這篇這篇的教學文章。

在這次的專案之中,我們將會應用 SwiftUI,來建立一個電影預告片 App。

Movie Trailers App using SwiftUI

在建立這個 App 的過程之中,我們可以學習到:

  • 如何從資源檔案載入 JSON 數據
  • 如何使用 ForEachActionSheetImage、以及像是 Rectangle 的形狀
  • 如何使用 UI 元件,像是 ScrollViewTabViewVStackHStackZStackNavigationLink、Button 等
  • 如何使用 Property Observers,像是 StateBinding
  • 如何透過 UIViewRepresentableUIViewControllerRepresentable,使用 SwiftUI 中的 UIKit ViewControllers
  • 如何使用 SFSafariViewControllerUIPageViewController

1. 創建 Model 類別與 JSON 檔案

從上面的程式碼中,我創建了一個 Model 類別,裡面包含了每個電影的詳細資料,像是:idthumbnailtitledescriptiontrailerLinkcatagory 等等。

接著,我創建了一個 Catagory,幫助我們以電影類型將電影分類。

這個 Movies.json 檔案包含了一個我創建的的靜態 json,用於開發這個電影 App。

JSON 解碼

我使用 JSONDecoder() 將 JSON 從資源檔案 movies.json 解析出來。現在,我們可以在任何包含了電影資料陣列的 Views 中使用 moviesData

2. 儀表板 (Dashboard)

在這個章節中,我們會開發一個電影預告片 App 的儀表板,成果看起來會像這樣:

Dashboard Screen

電影項目的設計

我使用了 VStack 來將 Image 與電影資料的描述對齊,例如 Title 以及 Description。

以下是 Xcode 對於特定電影項目的預覽:

SwiftUI-Xcode Preview For Single Movie Item

設計橫列 (Row) 的電影項目

我使用了 VStackMovieItemsMovie Catagory title 垂直對齊。然後,再使用 ScrollView 將每一個 MovieItem 水平對齊,並形成一個橫列。另外,我再使用 NavigationLink,引導使用者到呈現電影詳細資料的 MovieDetail 頁面。

我也使用了 ForEach 來將 MovieItem 水平對齊,按陣列元素組成一個橫列的電影項目。

SwiftUI-Xcode Preview for Each Row of Movie Items

設計建基於電影類型的 Movie Row

我利用了 Groupingfiltering,依照電影類別來創建一個陣列,我們也向它提供 NavigationView,給儀表板設標題,同時也可以在視圖之間進行導航。

我們藉由使用 Catagories ForEachKeys,創建電影類別的橫列。

現在,我們已經為每種電影類別創建了一個 MovieRow。每個類別都有一個元素的陣列,讓 MovieItem 可以填入不同的 MovieRow 之中。

SwiftUI-Designing Movie Row Based On Genres

使用 UIKit 來建構介面 — 設計精選電影 (Featured Movie)

SwiftUI 能與所有 Apple 平台現有的 UI 框架完美協作。舉例來說,你可以將 UIKit 的視圖和視圖控制器放到它的視圖裡面,反之亦然。

這部分的教學中,我會向你展示如何將精選電影清單由主選單轉換成 UIPageViewControllerUIPageControl 的包裝實例。

你將會使用 UIPageViewController 來展示 SwiftUI 視圖的輪播,並使用狀態變數與綁定,來協調使用者介面中的數據更新。

1) 創建呈現 UIPageViewController 的視圖

為了在 SwiftUI 中呈現 UIKit 視圖與視圖控制器,我們需要創建類別來遵循 UIViewRepresentableUIViewControllerRepresentable 協定。

根據開發者自定義的類別,創建並配置它們所呈現的 UIKit 類別,SwiftUI 就會管理其生命週期,並適時更新。

步驟 1:創建一個新的 SwiftUI 視圖檔案,命名為 PageViewController.swift。接著,宣告 PageViewController 類別,並遵循 UIViewControllerRepresentable

頁面視圖控制器儲存了一個 UIViewController 實例的陣列。以下是用來在電影之間滾動的頁面。

然後,添加兩個 UIViewControllerRepresentable 協定所需要的實作。

步驟 2:加入一個 makeUIViewController(context:) 方法,以建立一個 UIPageViewController 並搭配所需要的配置。

當 SwiftUI 準備好顯示視圖的時候,就會呼叫一次這個方法,並接手管理視圖控制器的生命週期。

步驟 3:加入一個 updateUIViewController(_:context:) 方法,它會呼叫 setViewControllers(_:direction:animated:) 來呈現在陣列之中的第一個視圖控制器。

步驟 4:創建一個新的 SwiftUI 視圖檔案,命名為 PageView.swift,並更新 PageView 類別來宣告 PageViewController 為子視圖。

  • 請注意,泛型建構器 (generic initializer) 取用了一組視圖陣列,並將每一個視圖嵌套到 UIHostingController 裡。
  • UIHostingControllerUIViewController 的一個子類別,代表著一個在 UIKit 上下文之中的視圖。

步驟 5:在進入下一步之前,請將 PageView 預覽固定在畫布上 ── 這個 View 就是我們所有動作執行的所在地。

步驟 6:為了查看預覽,我們需要創建一個設計來顯示精選電影。我已經創建了一個像圖卡的結構視圖,其中包含了以 ZStack 對齊的標題及縮圖。你可以參考以下的程式碼片段:

2) 建立視圖控制器的資料來源

PageViewController 使用了 UIPageViewController 來呈現 SwiftUI 視圖中的內容,現在是時候來實作切換頁面的滑動功能了。

一個呈現 UIKit 視圖控制器的 SwiftUI 視圖,能夠定義它管理的 Coordinator 類型,並提供一部分的視圖內容。

步驟 1:PageViewController 裡頭宣告一個巢狀的 Coordinator 類別。

  • SwiftUI 管理了 UIViewControllerRepresentable 類別的協調者 (coordinator),並在上文創建的方法被呼叫時,提供它為內容的一部分。

步驟 2:加入另一個方法到 PageViewController,以創造協調者。

  • SwiftUI 會在 makeUIViewController(context:) 之前呼叫這個 makeCoordinator() 方法,這麼一來,你就能在配置視圖控制器時存取協調者物件。
  • 你可以使用這個協調者來實作常見的 Cocoa 設計模式,像是委派 (delegates)、數據來源 (data sources)、或是透過 target-action 的方式來回應使用者事件。

步驟 3:使 Coordinator 類別遵循 UIPageViewControllerDataSource 協定,並實作兩個必要的方法。

  • 這兩種方法建立了各個視圖控制器之間的關係,如此一來你就能在它們之間前後滑動。

步驟 4:加入協調者為 UIPageViewController 的數據來源。

3) 追蹤在 SwiftUI 視圖狀態中的頁面

為了準備加入客製化的 UIPageControl,你需要一個方法,讓你可以追蹤當前在 PageView 內的頁面。

為此,你會在 PageView 裡宣告 @State 屬性,並將該屬性的綁定傳遞給 PageViewController 視圖。PageViewController 會更新綁定以符合看得見的頁面。

步驟 1:首先,加入一個 currentPage 綁定作為 PageViewController 的屬性。

  • 除了宣告 @Binding 屬性以外,你也要更新 setViewControllers(_:direction:animated:) 的呼叫,並將數值傳遞給 currentPage 綁定。

步驟 2:PageView 裡面宣告 @State 變數,並在創建 PageViewController 時將綁定傳遞給屬性。

  • 使用 $ 符號來創建一個數值為儲存狀態的綁定。

步驟 3:加入一個帶有 currentPage 屬性的文字視圖,那麼你就能關注著 @State 屬性的數值。

你可以觀察到當你在頁面之間滑動時,數值並沒有改變。

步驟 4:PageViewController.swift 檔案裡面,讓 Coordinator 類別遵循 UIPageViewControllerDelegate 協定,並加入 pageViewController(_:didFinishAnimating:previousViewControllers:transitionCompleted completed: Bool) 方法。

  • 因為每當頁面切換動畫完成時,SwiftUI 就會呼叫這個方法。你可以找到當前視圖控制器的索引並更新綁定。

步驟 5:除了資料來源之外,將協調者指派為 UIPageViewController 的委派。

  • 在雙向連結都綁定的情況下,每次滑動頁面之後,文字視圖就會更新並且展示正確的頁碼。

看看以下關於 PageViewController 的程式碼片段:

4) 加入客製化的頁面控制 (Page Control)

現在我們可以將客製的 UIPageControl 加入到視圖當中,並且封裝到 SwiftUI 的 UIViewRepresentable 視圖當中。

步驟 1:創建一個新的 SwiftUI 視圖檔案,命名為 PageControl.swift。更新一下 PageControl 型別,使它遵循 UIViewRepresentable 協定。

  • UIViewRepresentableUIViewControllerRepresentable 都擁有同樣的生命週期,其方法都會對應到底層的 UIKit 類型。

步驟 2:將文字框替換成頁面控制,以及將 VStack 切換成 ZStack 來進行佈局。

  • 因為你將頁面數目及綁定傳遞給當前的頁面,所以頁面控制已經呈現了正確的數值。
  • 接著,使頁面控制具有互動性,那麼使用者就能夠透過輕輕點擊某一邊來在頁面之間做切換。

步驟 3:在 PageControl 裡創建一個 Coordinator 巢狀型別,並加入 makeCoordinator() 方法來創建與回傳一個新的協調者。

  • 因為 UIControl 的子類別(像是 UIPageControl)使用了 target-action 模式,而非委派模式,所以這個 Coordinator 實作了一個 @objc 方法來更新當前的頁面綁定。

步驟 4:加入協調者為 valueChanged 事件的目標,並指定 updateCurrentPage(sender:) 方法為需要執行的動作。

步驟 5:現在嘗試一下所有不同的互動吧!PageView 展示了 UIKit 與 SwiftUI 視圖和控制器的協同工作。看看以下的程式碼片段,確認 PageControl 的完整程式碼:

5) 將精選電影整合到儀表板中

現在,我們需要將精選電影清單加入到儀表板當中。為此,我們要先從電影資料中過濾出精選電影:

接著,我們將每個精選電影映射到 Pageview 之中。Pageview 包含了可以滾動瀏覽每套電影的頁面控制。

參考下列的程式碼片段,將 FeaturedMovies 清單加入到儀表板的頂端:

FeaturedMovieList — Xcode Preview
FeaturedMovies List With Page Control

我們加入 Movie Row 的程式碼,在精選電影下方按電影類型顯示電影:

DashboardView — Xcode Preview

6) 在儀表板加入 TabView

TabView 利用互動式的使用者介面元素,在多個子視圖之間做切換。

為了使用 Tab 來創建使用者頁面,讓我們將視圖放到一個 TabView 之中,並將 tabItem(_:) 修飾符加入到每一個 Tab 的內容中。下面的程式碼就創建了一個有四個 Tab 的 Tabview:

  • 每一個 Tab Item 都由不同的 ImageText 視圖來客製化。
  • Apple 在 WWDC 2019 推出了 SF Symbols App,讓我們能夠以瀏覽這個 App 的方式來使用 Image。基本上,Apple 提供了免費符號讓我們在 App 中使用,而且使用上也非常容易。
  • SF Symbols 支援 iOS 13+watchOS 6+、以及 tvOS 13+ 平台。
  • Apple 提供了 SF Symbols App,讓你可以瀏覽、複製、和匯出任何可用的符號。你可以在這裡下載 App,它可以在 macOS 10.14 或更新版本上運行。

7) 導航到電影細節視圖 (Movie Detail View)

首先,我們需要將 List 嵌入到 NavigationView 裡面,而且我已經在設計 HomeView 的時候完成了:

這就像是將視圖控制器嵌入到一個導航控制器當中:你現在可以獲取所有導航的內容,像是導航列標題。請注意,.navigationBarTitle 是修飾 List 的,而不是 NavigationView。你可以在一個 NavigationView 內宣告多於一個視圖,而且每個視圖都能擁有自己的 .navigationBarTitle

我們將會預設得到一個較大的 MOVIES 標題。

Creating a Navigation Link

創造一個導航連結 (NevigationLink)

NavigationView 也啟用了 NavigationLink,它需要一個 destination 視圖和標籤。

HomeView 裡的 List 閉包中,使橫列視圖 Text() 變成一個 NavigationLink 按鈕。

SwiftUI-Navigation From FeaturedMovies List
Navigation For Movie Item

3. 電影細節畫面 (Movie Detail Screen)

在這個章節中,我們會開發電影細節畫面,成果看起來會像這樣:

Movie Detail Screen

設計預告片按鈕

在創建按鈕時,你必須提供兩部分的程式碼:

  1. 需要執行什麼 ── 在按鈕被使用者點擊或是選取之後,所需要執行的程式碼。
  2. 按鈕的樣子 ── 描述按鈕外觀的程式碼。

在上述的程式碼中,我已經依照自己的喜好客製化按鈕,我也使用了 State 屬性觀察器 (Property Observer),讓按鈕被點擊的時候改變 showingDetail 變數的數值。

在 SwiftUI,當屬性觀察器的數值被改變,它就會重新載入使用到該變數的視圖。因此,它將會展示出下文會深入解釋的 TrailerView

我使用了像是 frameforegroundColorbackgroundcornerRadius 等屬性,來依照我的喜好為按鈕做客製化設計。

Watch Trailer Button — Xcode Preview

設計電影細節畫面

在這個畫面,我使用了 navigationBarHidden(true) 來隱藏導航列,並同時使用了 edgesIgnoringSafeArea(.top) 來忽略螢幕頂端的安全邊界區域。

我使用了 ZStack 在電影細節畫面上方顯示電影的 ImageTitle,然後使用了 VStack 在標題的下方顯示了電影的描述

接著,我將 WatchButton 視圖整合到電影的描述的下方。參考下列程式碼來瞭解 MovieDetail 頁面:

MovieDetail — Xcode Preview

4. 預告片畫面 (Trailer Screen)

在這個章節中,我們會開發預告片的畫面,成果看起來會像這樣:

Trailer Screen - SwiftUI

將 SFSafariViewController 整合到 SwiftUI 之中

我使用 UIViewControllerRepresentable,將 SafariViewController 整合到 SwiftUI。

我們需要加入 makeUIViewController(context:) 方法,以創建一個具有我們所需配置的 SFSafariViewController。SwiftUI 會在準備好要顯示視圖的時候單次呼叫此方法,並管理視圖控制器的生命週期。

加入一個 updateUIViewController(_:context:) 方法,我們呼叫這方法就可以在關閉按鈕被點擊時,關閉視圖控制器。

現在,我們創建了一個名為 TrailerView 的新 SwiftUI 視圖,當 Movie Detail 頁面的 WatchTrailer 按鈕被點擊時,該視圖就會被呼叫。

以預告片連結 URL 呼叫 SafariView 之後,連結將會從 SafariViewController 載入,而預告片將會從 youtube 播放。

看看以預告片的 URL 來呼叫 SFSafariViewController的程式碼:

TrailerView — Xcode Preview

資源

你可以從這個 GitHub 連結中,找到更多詳細的螢幕截圖與專案程式碼,只要你有需要,就可以參考看看。

這個專案已經更新成 Xcode 11+ 及 Swift 5.0 的版本。

結論

我希望你學習到如何使用 SwiftUI 來建立 App,以及如何從中運用不同的 UI 元件,並知道如何將 UIKit 整合到 SwiftUI。

我會考慮再為這個 App 實作更多功能,像是顯示電視節目及更多其他節目和電影的預告片,以嘗試更多 SwiftUI 的概念。

如果你喜歡這個 App,並且希望跟我實作更多功能,請 Follow 本專案的儲存庫。如此一來,只要我對這個電影預告片 App 作出了變更、或是添加了新的模組,你就能夠獲取通知。

你都可以在 Github 上面的 readme 檔案中,找到我開發本次 App 所參考的素材及文章。

希望你覺得這篇文章對你有所幫助。如果你有任何問題,歡迎在下方留言,我會盡量回覆,感謝!

本篇原文(標題:Building Movie Trailer App Using SwiftUI)刊登於作者 Medium,由 Shankar Madeshvaran 所著,並授權翻譯及轉載。

作者簡介:Shankar Madeshvaran,專注於 iOS 及 Xamarin 的開發者,我喜歡寫關於 iOS 與 Xamarin 概念的文章。歡迎在 GitHub 上關注我。

譯者簡介:HengJay,iOS 初學者,閒暇之餘習慣透過線上 MOOC 資源學習新的技術,喜歡 Swift 平易近人的語法也喜歡狗狗,目前參與生醫領域相關應用的 App 開發,希望分享文章的同時也能持續精進自己的基礎。
LinkedIn: https://www.linkedin.com/in/hengjiewang/
Facebook: https://www.facebook.com/hengjie.wang


此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。

blog comments powered by Disqus
Shares
Share This