簡介 SwiftUI & 用其建構一簡單的 App
在 2019 WWDC 大會上,Apple 發表了一令人振奮的消息,也就是 SwiftUI 的降臨,而 SwiftUI 和 UIKit 其實是對等的,都是用來處理 UI 的 framework,只不過他們的背後是非常大不同的,在使用上亦有非常大的差異,這篇文章的議程,大部份時侯也會把 SwiftUI 和 UIKit 一起討論之。而 SwiftUI 從六月發表至今,也好一段日子,網路上的學習文章也愈來愈多且豐富。不過呢,Apple 一直還在調整 SwiftUI 的語法,網路上比較早期的部份教學文章或解法甚至都已經無法使用了。所以,我想說的是,這篇文章裡的部份語法,亦有可能在短時間內也無法使用,若是大家有發現語法不能用了,可以留言給法蘭克。廢話不多說,就讓我們開始來認識 SwiftUI 吧,我將今天的教學文大致分為以下幾大項來說明之。
- UIKit 與 SwiftUI 的差異性。
- 使用 SwiftUI 為 User Interface 與 Storyboard 的不同之處。
- SwiftUI 的 Library 介面、Text 元件介紹。
- 實作一程式設計書的列表、點擊其中一項目可進入明細的 App。
- 總結。
在開始此教學前,請確定你的 MacOS 已升級至 10.15+、Xcode 已升級至 11+
UIKit 與 SwiftUI 的差異性
法蘭克將 UIKit 與 SwiftUI 的差異拆成以上幾個面向比較之,以下就分別來解釋之。
系統需求
UIKit 是從 Xcode 誕生就一直存在的 Framework;而 SwiftUI 則是 2019/6 WWDC 所發表的全新用來繪製 UI 的 Framework。因此,UIKit 無系統限制,而 SwiftUI 必須搭配 iOS13+ 和 MacOS10.15+。
底層語言
UIKit 底層為 Objecitve-C;而 SwiftUI 則是完完全全用 Swift 打造的 Framework。
語法簡潔度
SwiftUI 相較於 UIkit 語法更簡潔了。
跨平台
跨平台指的非跨 Android(但希望有那麼一天是可以支援的😀)。跨平台指的是使用 SwiftUI 所開發的專案,可以同時支援 macOS、watchOS、tvOS 等系統。引用一句 WWDC2019 SwiftUI 演講者所說的一句話。
Learn once, apply everywhere.
Automatic Preview
這是此次 SwiftUI 最大的亮點之一,所謂 Automatic Preview,意思指的是即時預覽,即我們一邊調整程式碼的同時,也可以立即看到修改後的結果。
自動支援進階功能
SwiftUI 本身即支援 Dynamic Type、Dark Mode、 Localization 等等。這邊特別來講一下 UIKit 和 SwiftUI 在文字設定上有關於 Dark Mode 的差異,UIKit 若是無特別指定文字的顏色(意即使用 Default 的選項),在 Light Mode 字體會是白色;反之在 Dark Mode 即會是白色,這點跟 SwiftUI 沒有特別的差異,但是 SwiftUI 除了 Default 外,還有 Secondary,如果還不喜歡的話,還有第三個選項,就是在 Assets 自行設定 Light Mode 和 Dark Mode 分別要顯示的顏色😀
使用 SwiftUI 為 User Interface 與 Storyboard 的不同之處
▼在 Xcode11 使用 Swift 語言開啟專案時,User Interface 多了一個 SwiftUI 的選項😁
▼使用 SwiftUI 為 User Interface 與使用 Storyboard 所開啟的專案,有非常大的差異性,也多了很多與既往不同的類別、工具介面等等。
利用 Xcode10 並使用 Swift 為 User Interface 和利用 Xcode11 並使用 SwiftUI 為 User Interface 開啟一新專案時,有何不同之處。
- 多了 SceneDelegate(如上圖的項次1)?
- Main.storyboard 不見了?
- ViewController → ContentView(如上圖的項次2)?
- 多了 Automatic Preview(如上圖的項次3)?
是的,法蘭克就要針對以上幾點來分別說明之。
多了 SceneDelegate?
SceneDelegate 為 Xcode11 所帶來的變化(可參考官方文件WWDC2019),放在 SwiftUI 提似乎不太合適,但是在接下來在提到 SwiftUI App 的生命週期時會帶到,所以這邊就大概提一下。AppDelegate 原來的職責為負責 App 的生命週期和 UI 生命週期,在 Xcode11 後,AppDelegate 將 UI 的生命週期(Scene Session)交給 SceneDelegate。原 Xcode10 使用 Swift 為 User Interface 的專案 Launch 的生命週期為 AppDelegate → ViewController,而使用 SwiftUI 為 User Interface 的專案則變成為 AppDelegate → SceneDelegate → ContentView,原本應該出現在 AppDelegate 的 applicationWillEnterForeground(_:) 等相關 App 到前、背景等相關的生命週期邏輯也都移至 SceneDelegate 裡了,method 名稱 application 的前綴字也都更改為 scene 了。
Main.storyboard 不見了?
Main.storyboard 不見了(WTF???)?是的,使用 SwiftUI 為 User Interface 的專案預設是沒有 Storyboard 的。那就一定會有人問,那 Controller 的入口點呢…?已不再是 ViewController 了,已改成 ContentView 了,而且,它也不是 Controller 而是 View!讓我們來看看圖大家就會比較清楚了。打開專案設定的 General Tab。
再來翻開 SceneDelegate.swift 來瞧瞧。
承上圖,從程式碼第 13~18 行看出 rootViewController 竟換人當了,原本是Main.storyboard 的 ViewController,現在由 ContenView 取而代之了。另外,我們所熟悉的 UIWindow 竟也換成出現在 SceneDelegate 了😀
多了 ContentView?
ContentView 的出現取代了 ViewController,雖說是取代,但前者是 View 而後者確是 UIViewController,可說是完完全全不一樣的東西,而生命週期也不一樣,以下來看看 ContentView 的生命週期,先看圖,後續再使用程式碼 來說明會比較清楚。
多了 Automatic Preview?
這是一個執行預覽的的區塊,當我們打完程式碼的同時,該區塊就會立即呈現結果,我覺得這是 Xcode11 最酷的地方。而 Automatic Preview 讓我不禁讓我聯想到開發 iOS 的兩個派系,純 Code 、非純 Code 學派,此兩學派經常在討論程式碼的一致性、直覺性,純 Code 總拿一致性抨擊對方,而另一方則是拿直覺性來回應之。不過,當 SwiftUI 降臨後的那一刻起,或許可以在這兩個學派間稍稍緩頰了🙂
SwiftUI 的 Library 介面、Text 元件介紹
這個小節先來為下個小節暖暖身,稍加熟悉 SwiftUI 的 Library 介面、最基本的 Text 元件、畫面同時存在多個 Text 元件時的處理方式。
SwiftUI 的 Library 介面介紹
要開啟 SwiftUI 的 Library 的以下兩種方式。
- 點選 Xcode 的右上角「+」。
- 使用快捷鍵 Command + Shift + L。
打開 Library 會看到如下圖,我將其分類成以下項目
1 => Views 區塊,如畫面我們可以看到熟悉的 Button、Date Picker 等等,而 1.1 的部份則是 Xcode 貼心的有將 Views 類別再用 Section 細分,讓開發者可以快速的找到自己想找的 View。
2 => Modifiers 區塊,Modifier 是一個全新的名詞,可以把他想像成 Views 的屬性,例如:切圓角、不透明度、Frame 的大小等等。
3 => Snippets 區塊,這是一既有的功能,放置常用的 Code,亦可放置自行定義的 Code。
4 => Media 區塊,這裡可以找到放在 Assets 裡的資源。
5 => Color 區塊,這裡可以找到放在 Assets 裡的資源。
Text 元件介紹
前頭講了那麼多,這邊終於要開始寫點東西,來刷刷成就感了。不過,在這之前先來了解 ContentView 的 Lifecycle 吧。
▼點擊 Resume 啟動 Automatic Preview。
而當我們點擊 Resume 按鈕後,ContentView 的 Lifecycle 也隨之啟動。忘了的話,文前有提到,稍加看一下,接下來要說明的部份會比較清楚。
如上圖的 17~21 行 => ContentView Lifecycle 中的 Initialization。
如上圖的 11~15 行 => ContentView Lifecycle 中的 State and Data Flows。
▼onAppear(perform:) 和 onDisappear(perform:) 則要手動加入,不過此兩 function 在 Auto Preview 是不會被執行到的,必須使用模擬器才行。
以上是 ContentView 的 Lifecycle。以下開始說明同 UIKit UILabel 的 Text 元件。我們就從萬種程式語言的起頭, ”Hello world” 開始說起。
▼打開一使用 SwiftUI 為 User Interface 的專案,已預設幫我們使用 Text 元件寫好 “Hello world”,我們只要點擊 “Resume” 即可從 Automatic Preview 看到 “Hello world”。
接著要說明分別透過三種方式來調整 Text 屬性,三種屬性分別是字體大小、水平對齊方式、字體顏色等等。
1. 字體大小。在 Text 上點選 Command + 滑鼠左鍵 → Show SwiftUI Inspector → LargeTitle。
2. 水平對齊方式。在 Text 上點選滑鼠左鍵 → Show the Attributes inspector → Alignment。
3. 字體顏色。點擊 Xcode 右上角的「+」→ Modifiers → 搜尋 foreground → 拖拉 foreground color 至 editor 中。
以上,已完成 Text 的基本設定。接著要再試著在 Text 上方再加入一 Text,如同 Vertical Stack View 一樣,同下圖。
▼點擊 Xcode 右上角的「+」→ Views → 搜尋 text → 拖拉 text 至 Automatic Preview 的 Hello World 上(要將 Automatic Preview 停止才能執行此動作)。
▼此時,我們看看 Editor 上的 Code,兩個 Text 被包在一 VStack 中。
Xcode 幫我們加上了 Vstack?沒錯,因為 body 變數,恆為只能回傳一物件。如下圖證明之,在 body 加上 return 後,compile 其實也是成功的,只是 Xcode 幫我們省略了。
這告訴我們,在開發時,要掌握一個原則, body 恆為只能 return 一物件。若有多個物件時,一定得放在 Stack 裡。
實作一程式設計書的列表、點擊其中一項目可進入明細的 App
文前已大概講述過從整個專案看 SwiftUI 的 Lifecycle、ContentView 的 Lifecycle、Text 元件的介紹,將以上的觀念拼湊起來,開始動手來實作一簡單的程式設計書列表,點擊某一項目可以進入明細的 App。而此 App 僅有兩個頁面,第一頁面為類 UITalbeView 的列表、第二頁則為點擊類 UITableViewCell 會進入的明細頁。在開始前,先來瞧瞧完成後的態樣😀
而法蘭克將該 App 拆成以下幾個部份來說明:
- 產生列表畫面來當成主頁面。
- 產生測試資料,並將其連結至列表裡。
- 產生點擊列表任一筆資料後,所會進入的明細頁面。
- 在主頁面產生 Navigation View 以用來將畫面 Push 到明細頁面,並且一併將資料傳遞過去。
產生列表畫面來當成主頁面
▼開啟一 User Interface 為 SwiftUI 的專案
▼點擊 Resume 啟動 Automatic Preview
▼點擊「+」→ 在 Library 的 Views 頁籤搜尋 Text → 將 Text 拖拉至 Automatic Preview “Hello World” 的 Text 上 → 檢查兩個 Text 是否被一個 VStack 所包住
▼點擊「+」→ 在 Library 的 Views 頁籤搜尋 Image → 將 Image 拖拉至 VStack 左邊 → 檢查兩個 Text + Image 是否被一個 HStack 所包住
接著在 Xcode 會出現 Compile Error 的 告警,告訴我們 Image 要有初始值,我們在暫用只有在 iOS13+ 特有的 SF Symbols 來產生圖片,有關於 SF Symbols 可參考。
▼在 HStack 上點擊左鍵 + command→ 選擇 Embed in List → 確認是否產生五個類 UITableViewCell 的畫面
以上已完成第一部份,接下來要將產生測試資料並顯示在畫面上,在這之前,先下載接下來會用到的圖片,再將其放置 Assets 下。
產生測試資料,並將其連結至列表裡
▼在 ContentView.swift 下建立測試資料
第 1~7 行 => 建立程式設計書的 struct,並得必須遵行 Identifiable 的協議,並實作 id 這個 property。
第 10~16 行 => 生成五本書的物件。
▼在 ContentView 撰寫連結測試資料的相關邏輯
第 3 行 => 宣告測試資料。
第 6~12 行 => 將所有的書 programmingBooks 傳入 List,並使用閉包所回傳的單一本書 programmingBook 取代掉圖片、書名、出版社等等。
▼將測試資料注入 ContentView 裡
第 3 行 => 原本 ContentView 並無傳入值,現將測試資料傳入。
以上已完成囉,試著按「Resume」重新 Building 並啟動 Automatic Preview 試試。雖然完成了,但 UI 有點美中不足,例如:圖片無切圓角、文字無靠左對齊等等。以下繼續完成那美中不足的地方。
▼調整 UI 美中不足的地方
第 2 行 => 利用 Modifier 的 clipShape 切圓角。
此屬性可手動加入,亦可在 Modifier 裡找到它,後者,只需利用拖拉的方式,將其拖拉至 Automatic Preview 上即可。
第 4 行 => 調整書名的字體大小為 Title。
第 7~8 行 => 調整出版社的字體大小為 SubHeadline、字體顏色為 Gray。
▼完成圖
產生點擊列表任一筆資料後,所會進入的明細頁面
▼建立 DetailView.swift
▼建立明細頁面上的參數、元件
第 3~5 行 => 建立上主頁所要帶入的參數,書名、內容、書的圖片。
第 8~12 行 => 建立一 VStack 裡頭由上而下,依序為書的圖片、書名、內容。
第 13 行 => 為該頁面加上 Title。
▼生成給 Automatic Preview 用的資料
第 3 行 => 生成給 Automatic Preview 用的資料。此資料僅在該頁面的 Preview 時有作用,當主頁有資料傳進來時,此資料則不會有任何作用。
▼調整 UI 美中不足的地方
第 9 行 => 利用 Modifiers 的 padding 讓容器(這裡指的是VStack)裡的元件距離容器有一定的邊距。
在主頁面產生 Navigation View 以用來將畫面 Push 到明細頁面,並且一併將資料傳遞過去
▼將 Navigation View 加入 ContentView.swift 裡
▼將 List 移動到 Navigation View 的區塊裡
▼為 Navigation View 加上 Title
這邊很容易加錯地方,要注意是加在 NavigationView 的區塊裡,而不是外面。
▼為 List Item 加上 Navigation Link,以達到點擊時,Push 到明細頁的動作。點擊 Xcode 右上角的「+」→ Views → 搜尋 NavigationLink → 拖拉 NavigationLink 至 editor 中
▼實例化一 DetailView 的物件,並傳入 NavigationLink 的 destination 參數名
▼將原 Image 區塊移動到 NavigationLink 區塊裡
▼最後完成的 body 區塊程式碼
終於完成啦,重新啟動 Automatic Preview 試試😀
總結
學習 SwiftUI 雖不是當務之急,因既有的專案目前都還有支援 iOS13 以下的版本,是說也可以用 SwiftUI 寫,但我想…應該沒有人會想在同一功能撰寫兩套不種的邏輯。不過,在未來兩、三年內,App 最低支援版本有可能只會支援 iOS13 以上的版本,為什麼這樣說呢?因為最大宗且多人數都會參照的 Line App 目前最低支援已經來到 iOS11 了,竟只和現在最新的版本差兩版而己!所以,以這樣的邏輯推算,真的很有可能在未來兩、三年內,App 就全面支援 iOS13 以上版本,屆時就可以直接考慮用 SwiftUI 開發囉。另外,千萬不要以為都不用 SwiftUI 開發就不用學囉,在多人開發的專案裡,有可能有人會使用 SwiftUI 開發,屆時要維護,還是得看得懂別人在寫什麼吧。最後,SwiftUI 是未來 Apple 的重心所在,愈早學習就比別人領先一步。
如果您喜歡我的文章,請多按幾下「拍手」給我鼓勵,或是按「follow」讓我持續提供好文章給您。