第 3 章
使用 Swift 與 SwiftUI 建立你的第一個 App
Learn by doing. Theory is nice but nothing replaces actual experience.
– Tony Hsieh
現在你應該已經安裝好 Xcode,並且對 Swift 語言有一些了解。如果你跳過了前兩章的內容,則我強烈建議你在此暫停,並返回閱讀它們,你必須先安裝 Xcode,才能完成本書中的所有練習。
在上一章中,你已經嘗試使用 SwiftUI 來建立一些 UI 元件。在本章中,我們將深入研究並為你全面介紹 SwiftUI 框架,另外你將有機會建立你的第一個 iOS App。
SwiftUI 介紹
2019 年的 WWDC 中,Apple 宣布了一個名為「SwiftUI」的全新框架,這讓所有的開發者都大為驚訝,它不僅改變了開發 iOS App 的方式,也是自 Swift 問世以來 Apple 開發者的生態系統(包括 iPadOS、macOS、tvOS 與watchOS)的最大轉變。
SwiftUI 是一種創新且極為簡單的方式,透過 Swift 的強大功能,可在所有的 Apple 平台上建立使用者介面。只需使用一套工具與 API,即可為所有的 Apple 裝置建立使用者介面。
開發者對於「應該直覺設計 App UI,還是用程式碼編寫 UI」一事已爭論許久,而 SwiftUI 的導入就是 Apple 對於這個問題的回應。透過這個創新的框架,Apple 對開發者提供了一個建立使用者介面的全新方式。請參見圖3.1 所呈現的畫面,並花些時間查看其所對應的程式碼。
隨著 SwiftUI 的發布,你現在可以使用宣告式(Declarative )的 Swift 語法來開發 App 的 UI,這表示編寫 UI 程式碼的過程變得更簡單、直覺。和目前的 UI 框架(如UIKit )相比,你可以使用更少的程式碼來實現相同的 UI。
宣告式程式設計 vs 指令式程式設計
和 Java、C++、PHP 與 C# 類似,Swift 是一個指令式程式語言(Imperative Programming), 不過 SwiftUI 以其為一個宣告式 UI 框架(Declarative UI Framework )而自豪,該框架可以讓開發者以宣告式的方式建立 UI。而「宣告式」一詞是什麼意思呢?它和指令式程式設計有何不同呢?更重要的是,這個變更對你編寫程式的方式有什麼影響呢?
對於剛接觸程式設計的人來說,可能不需要立即去關心兩者之間的差異,因為一切對你而言都是新的內容,不過如果你有一些物件導向程式設計的經驗,或者之前曾經使用 UIKit 開發過,則這個典範轉移(Paradigm Shift )會顯著影響你建立使用者介面的方式, 你可能需要忘記一些舊思維並學習新觀念。
那麼,指令式程式設計與宣告式程式設計之間有何不同之處呢?如果你到維基百科搜尋這兩個專有名詞,你會找到以下的定義:
「在電腦科學中,指令式程式設計是一種使用語句來變更程式狀態的程式設計典範。就像自然語言中命令式語氣表達指令的方式一樣,指令式程式是由電腦執行的指令所組成。
在電腦科學中,宣告式程式設計是一種建立電腦程式的結構與元件風格的程式設計典範,它表達的是運算邏輯,而不描述控制流程。」
如果你沒有電腦科學背景,則很難理解其實際的差異,讓我用一個相關的類比來解釋其中的差異處。
這裡不將重點放在程式設計上,我們談一下披薩的烹飪(或任何你喜歡的料理)。假設你正在指示其他人(助手)去準備一個披薩,你可以使用指令式或宣告式的方式來進行。要以指令式烹飪披薩,你需要像食譜一樣來明確告訴助手每個指示:
- 加熱到 550°F 或更高溫度,至少要 30 分鐘。
- 準備一磅麵團。
- 將麵團揉成 10 英吋大小的圓。
- 將蕃茄醬以湯匙舀入披薩的中間,並均勻塗抹至邊緣。
- 再撒上一些配料(包括洋蔥、切片蘑菇、義式辣味香腸、煮熟的香腸、煮熟的培根、切 塊的辣椒)與起司。
- 將披薩烘烤 5 分鐘。
另一方面,如果你選擇以宣告式的方式來烹飪披薩,則不需要逐步說明,相反的,你只想要描述你需要做什麼樣的披薩,例如:你喜歡厚皮或者薄皮?想要義式辣味香腸與培根等配料、還是經典的瑪格莉特番茄紅醬?披薩的直徑要 10 吋或者 16 吋?透過傳達你的喜好,助手可處理剩下的事情,並相應地烹飪披薩。
這就是指令式與宣告式的主要不同之處。現在回到 UI 程式設計,指令式 UI 程式設計需要開發者編寫詳細的指令,來佈局 UI 並控制其狀態;反之,宣告式 UI 程式設計則可讓開發者描述 UI 的外觀,以及它應該如何回應狀態變更。
宣告式的程式碼風格將讓程式碼更易於閱讀與理解,除此之外,SwiftUI 框架可以讓你以更少的程式碼來建立使用者介面。例如:假設你的任務是要在 App 中建立一個心形按鈕,這個按鈕應該放置於螢幕中心,並回應使用者的觸控;當使用者點擊這個心形按鈕時,它的顏色應從紅色變為黃色,而當使用者按住這個心形時,它應以動畫方式放大。
參考一下圖 3.2,這是實作心形按鈕所需要的程式碼,大約 20 行的程式碼,你就可以建立一個帶有縮放動畫的互動式按鈕,而這就是宣告式 UI 框架的強大之處。
使用 SwiftUI 建立你的第一個 App
介紹完 SwiftUI 框架的背景資訊,如我常說的一句話:「你必須親自動手寫程式來學習程式設計」,現在是時候開啟Xcode 並使用SwiftUI 來編寫你的第一個 iOS App。在本章的其餘部分中,你將編寫程式碼來嘗試不同的 UI 元件,例如:文字、圖片、堆疊視圖。此外,你將學習如何偵測點擊手勢。透過結合所有的技術,你最終將建立你的第一個 App。
首先開啟 Xcode,並使用 iOS 類別下的「App」模板來建立一個新專案,點選「Next」按鈕來進入下一個畫面,如圖 3.3 所示。
接下來,設定專案名稱為「HelloWorld」。「Hello World」是初學者要去建立的第一個程式,其是一個在裝置螢幕上輸出「Hello World」文字的簡單程式。雖然你的第一個 App 會比這個程式更加複雜,但讓我們遵循傳統,將專案名稱命名為「HelloWorld」,如圖 3.4 所示。
「組織識別碼」(Organization Identifier)是你的 App 的唯一識別碼,這裡我使用「com. appcoda」,不過你在這裡應該填入你自己的值,如果你有自己的網站,可將其設定為反向域名。另外,你可以使用「com.」,例如:你的名字叫做「Pikachi」,則組織識別碼可填寫為「com.pikachi」。
Xcode 現在支援兩種建立使用者介面的方式,由於本書是與SwiftUI 有關,因此請將「介面」(Interface )選項設定為「SwiftUI」,對於程式語言(Programming Language )則將其設定為「Swift」。
「Include Tests」選項,你可不用勾選;點選「Next」按鈕繼續,接著 Xcode 會詢問你儲存「HelloWorld」專案的位置,請選擇你的Mac 電腦上的任何資料夾(例如:桌面)。你或許會注意到有個「版本控制」(Source Control )的選項,這裡不勾選它,本書不會用到這個選項,最後按下「Create」按鈕繼續。
當你確認後,Xcode 會自動建立「HelloWord」專案,畫面如圖3 .5 所示。
熟悉 Xcode 工作區
在我們開始實作「Hello World」App 之前,讓我們花幾分鐘來快速瀏覽Xcode 的工作區環境。左側窗格是「專案導覽器」(Project Navigator ),你可在此區域中找到所有的專案檔案;工作區中心部分是「編輯區」(Edit Area ),你可此區域中進行所的編輯工作(例如:編輯專案設定、原始碼檔案、使用者介面等)。
依照檔案類型的不同,Xcode 會在編輯區向你顯示不同的介面,例如:你在專案導覽器中選取「HelloWorldApp.swift」,Xcode 會在中心區域顯示原始碼, 如圖 3.8 所示。Xcode 內有數種主題供你選擇,例如:如果你喜愛深色主題,則可以到選單並選擇「Xcode → Preferences → Themes」來做變更。
如果你選取了「ContentView.swift」檔,Xcode 會自動調整程式碼編輯器的大小,並在其旁邊顯示一個額外窗格,此為 ContentView 的預覽窗格。如果你看不到設計畫布,則可以到 Xcode 選單並選擇「Editor → Canvas」來啟用它。
設計畫布可顯示 SwiftUI 程式碼的預覽,Xcode 會依照你在模擬器選項中所選的模擬器(例如:iPhone 14/15 Pro )來渲染預覽。
為了給自己更多的空間來編寫程式碼,你可以隱藏專案導覽器(Project Navigator )與檢閱器(Inspector ),如圖 3.6 所示。如果你想調整預覽大小,則使用右下角的放大圖示。
首次執行你的 App
至目前為止,我們還沒有撰寫一行程式碼。ContentView 中的程式碼是由 Xcode 產生的,在我們編寫自己的程式碼之前,讓我們試著使用內建的模擬器來執行 App,這將使你了解如何在 Xcode 中建立與測試你的 App。在工具列中,你應該會看到「Run」按鈕(亦是「Play」按鈕),如圖 3.7 所示。
Xcode 的「Run」按鈕是用於建立 App,並在選定的模擬器中執行它。在上述的範例中, 模擬器設定為「iPhone 15 Pro」。如果你點選「iPhone 15 Pro」按鈕,你將看到可用的模擬器清單,例如:iPhone SE 與iPhone 15 Pro Max,我們使用「iPhone 15 Pro」作為模擬器來測試。
當選擇後,你可以按下「Run」按鈕來在模擬器中載入你的 App,圖 3.7 顯示了 iPhone 15 Pro 的模擬器。要結束App 時,只需點擊工具列中的「Stop」按鈕。
試著選擇另一個模擬器(例如:iPhone SE)並執行 App,你將看到螢幕上顯示另一個模擬器。最新版本的 Xcode 可以讓開發者同時執行多個模擬器,如圖 3.8 所示。
這個模擬器的工作原理和 iPhone 實機非常相似,你可以點擊「home」按鈕(或按下 shift-command-h 鍵)來開啟主畫面,它還有一些內建的 App,只需使用一下,便可熟悉 Xcode 和模擬器環境。
處理文字
現在你應該熟悉 了Xcode 工作區,是時候檢查 SwiftUI 程式碼。在 ContentView 中產生的範例程式碼已經向你展示如何顯示單行文字。你初始化 Text 物件,並將欲顯示的文字(例如:Hello World )傳送給它,如下所示:
Text("Hello World")
如此,預覽畫布會在螢幕上顯示「Hello World」,這是建立一個文字視圖的基本語法。你可以任意變更文字內容,畫布應該會立即顯示更改結果,如圖 3.9 所示。
變更字型與顏色
在 SwiftUI 中,你可以呼叫名為「修飾器」(Modifier)的方法來變更控制元件的屬性(例如:顏色、字型與粗細)。假設你想要將文字加粗,你可以使用 fontWeight 修飾器,並指定你想要的字型粗細(例如:.bold ):
Text("Stay Hungry. Stay Foolish.").fontWeight(.bold)
你可以使用點語法(dot syntax )來存取修飾器。每當你輸入一個點時,Xcode 會顯示可使用的可能修飾器或值。例如:當你在 fontWeight 修飾器中輸入一個點時,你將看到各種字型粗細的選項,你可以選擇「bold」來加粗文字;如果你想要讓它更粗一點,則可以選擇「heavy」或「black」,如圖 3.10 所示。
透過使用 .bold 值來呼叫 fontWeight 修飾器,它實際上會回傳一個加上粗體字的視圖。SwiftUI 有趣之處在於,你可以進一步將此新視圖串接其他修飾器,例如:你想要讓粗體文字更大一點,則可以編寫程式碼如下:
Text("Stay Hungry. Stay Foolish.").fontWeight(.bold).font(.title)
font 修飾器可讓你變更字型屬性。在上列的程式碼中,我們指定使用 title 字型來放大文字。SwiftUI 內有幾種內建的文字樣式,包括 title、largeTitle、body 等,如果你想要進一步加大字型大小,則將「.title」替換為「.largeTitle」。
如果我們繼續在同一行程式碼中串接多個修飾器,則程式碼會變得難以閱讀,因此我們通常會將程式碼拆成多行,並按照以下格式編寫:
Text("Stay Hungry. Stay Foolish.")
.fontWeight(.bold)
.font(.title)
功能是相同的,但我相信你會發現到上列的程式碼更易於閱讀。我們將在本書的其餘部分使用此程式碼的編寫慣例。
font 修飾器還可以讓你更改文字設計,例如:你想要字型圓滑,可以將 font 修飾器編寫如下:
.font(.system(.title, design: .rounded))
這裡你指定使用有 title 文字樣式以及 rounded 設計的系統字型,預覽畫布應該會立即對變更做出回應,並向你顯示圓體文字,如圖 3.11 所示。
運用按鈕
「按鈕」是另一個你需要了解的常見 UI 元件,它是一個非常基本的 UI 控制元件,能夠處理使用者的觸碰,並觸發特定的動作。
要使用 SwiftUI 建立按鈕,你只需要使用下列的程式碼片段來建立按鈕:
Button {
// What to perform
} label: {
// How the button looks like
}
建立按鈕時,需要提供兩個程式碼區塊:
- 欲執行的動作 - 使用者點擊或選取按鈕後要執行的程式碼。
- 按鈕外觀描 - 描述按鈕外觀和感覺的程式碼區塊。
例如:如果你只想將 Hello World 標籤變成一個按鈕,你可以更新程式碼如下:
struct ContentView: View {
var body: some View {
Button {
} label: {
Text("Hello World")
.fontWeight(.bold)
.font(.system(.title, design: .rounded))
}
}
}
即使我們沒有指定任何的後續動作,「Hello World」文字也會變成一個可點擊的按鈕, 文字顏色會自動變更為藍色,因為這是iOS 中按鈕的預設文字。
你可以在預覽中點擊按鈕來測試一下,如圖 3.12 所示。儘管按鈕不執行任何動作,但你在點擊按鈕時應該會看到閃爍效果。
自訂按鈕樣式
與 Text 類似,你可以透過加上一些修飾器來自訂按鈕的顏色,例如:你可以加上f oregroundStyle 與background 修飾器來製作一個紫色的按鈕。
Button {
} label: {
Text("Hello World")
.fontWeight(.bold)
.font(.system(.title, design: .rounded))
}
.foregroundStyle(.white)
.background(.purple)
更改後,按鈕應該如圖3.13 所示。
如你所見,按鈕看起來不甚理想,在文字四周加上一些間距不是會更好嗎?為此,你可以使用padding 修飾器,如圖 3.14 所示。
SwiftUI 也可以非常輕鬆地建立圓角按鈕,你只要加上 clipShape 修飾器,並設定其值為 RoundedRectangle(cornerRadius: 20),如下所示:
.clipShape(RoundedRectangle(cornerRadius: 20))
cornerRadius 的值描述了按鈕的圓角程度。較大的值會產生更圓的角,而較小的值會產生尖角,你可以將圓角半徑改為其他值來查看效果
加入按鈕動作
如果按鈕不執行任何動作,那麼它就毫無用處。我們的目標是在點擊按鈕後說話,它會說:「Hello World!」。
這個任務可能看起來具有挑戰性,因為它涉及文字轉語音的功能,然而 Apple 讓它變得非常簡單,即使對於初學者來說也是如此。
如前所述,iOS SDK 內建了許多出色的框架來供開發者使用。在我們的例子中,我們使用 SwiftUI 框架來建立使用者介面,為了實現文字轉語音的功能,我們可以依靠 AVFoundation 框架。
在使用該框架之前,我們必須在程式碼的開頭匯入它。在 import SwiftUI 的下方插入以下的 import 敘述:
import AVFoundation
接下來,在 ContentView 中宣告一個變數來建立與存放語音合成器,如圖 3.16 所示:
let synthesizer = AVSpeechSynthesizer()
然後更新 Button 的程式碼如下:
Button {
let utterance = AVSpeechUtterance(string: "Hello World")
utterance.voice = AVSpeechSynthesisVoice(identifier: "com.apple.speech.synthesis.voice.Fred")
synthesizer.speak(utterance)
} label: {
Text("Hello World")
.fontWeight(.bold)
.font(.system(.title, design: .rounded))
}
.padding()
.foregroundStyle(.white)
.background(.purple)
.clipShape(RoundedRectangle(cornerRadius: 20))
這裡我們在程式碼區塊中加入三行程式碼,這就是你需要指示 iOS 為你朗讀一段文字的程式碼。第一行程式碼指定文字(即「Hello World」),第二行程式碼將語音設定為英式英文,最後一行是使用選定的聲音來說出文字。
要測試 App,你需要在模擬器中執行 App,點擊「Play」按鈕,並執行 App。要測試文字轉語音,則點擊「Hello World」按鈕來進行測試。
堆疊視圖介紹
你的第一個 App 運作良好,對吧?只需要大約 10 行的程式碼,你就已經建立一個可以將文字翻譯為語音的 App。目前,該按鈕是設計說出「Hello World」,如果你想在「Hello World」按鈕上方建立另一個會說不同句子或文字的按鈕時怎麼辦?如何排列 UI 佈局呢?
SwiftUI 提供了一種名為「堆疊視圖」(Stack View )的特殊類型視圖,供你建立複雜的使用者介面。更具體地說,堆疊視圖可讓你在垂直或水平方向排列多個視圖(或UI 元件)。例如:如果你想在「Hello World」按鈕上方加入一個新按鈕,你可以將這兩個按鈕嵌入到 VStack 中,如下所示:
VStack {
// New Button
// Hello World Button
}
VStack 是一個用於垂直佈局視圖的垂直堆疊視圖,在 VStack 中的視圖順序決定了嵌入視圖的排列方式。在上列的程式碼中,新按鈕將放置在「Hello World」按鈕的上方。
現在我們來修改原先的程式碼,以查看其實際的變化。要將「Hello World」按鈕嵌入到 VStack 中,你可以按住 control 鍵並點選 Button,在內容選單中選擇「Embed in VStack」, 然後 Xcode 會將「Hello World」按鈕包裹在 VStack 視圖中,如圖 3.18 所示。
在 VStack 中嵌入「Hello World」按鈕後,複製「Hello World」按鈕的程式碼來建立一個新按鈕,如下所示:
VStack {
Button {
let utterance = AVSpeechUtterance(string: "Hello Programming")
utterance.voice = AVSpeechSynthesisVoice(identifier: "com.apple.speech.synthesis.voice.Fred")
synthesizer.speak(utterance)
} label: {
Text("Hello Programming")
.fontWeight(.bold)
.font(.system(.title, design: .rounded))
}
.padding()
.foregroundStyle(.white)
.background(.yellow)
.clipShape(RoundedRectangle(cornerRadius: 20))
Button {
let utterance = AVSpeechUtterance(string: "Hello World")
utterance.voice = AVSpeechSynthesisVoice(identifier: "com.apple.speech.synthesis.voice.Fred")
synthesizer.speak(utterance)
} label: {
Text("Hello World")
.fontWeight(.bold)
.font(.system(.title, design: .rounded))
}
.padding()
.foregroundStyle(.white)
.background(.purple)
.clipShape(RoundedRectangle(cornerRadius: 20))
}
將新按鈕的標籤改為「Happy Programming」,並且其背景顏色也更新為「.yellow」。除了這些變更之外,AVSpeechUtterance 的字串參數更改為「Happy Programming」, 你可以參考圖 3.19 來進行修改。
以上就是如何垂直排列兩個按鈕的方法,你可以執行App 來快速測試。「Happy Programming」按鈕的工作原理與「Hello World」按鈕完全相同,但它說的是「Happy Programming!」。
了解方法
在結束本章之前,我來介紹另一個基本的程式設計觀念。請再看一下 ContentView 的程式碼,這兩個按鈕有很多的相似之處以及重複的程式碼,其中一個重複是按鈕的動作區塊的程式碼,如圖 3.20 所示。
這兩個程式碼區塊幾乎相同, 除了要讀取的文字不同而已, 一個是「Happy Programming」,另一個是「Hello World」。
在 Swift 中,你可以為這類重複性任務定義一個方法。在這個例子中,我們可以在 ContentView 中建立一個名為「speak」的方法,如下所示:
func speak(text: String) {
let utterance = AVSpeechUtterance(string: text)
utterance.voice = AVSpeechSynthesisVoice(identifier: "com.apple.speech.synthesis.voice.Fred")
synthesizer.speak(utterance)
}
func 關鍵字是用來宣告一個方法,在func 關鍵字之後是方法的名稱。這個名稱識別該方法,並使得該方法可以在程式碼中的其他地方輕鬆地呼叫。另外,方法可以接受參數作為輸入,參數是在括號內定義。每個參數都應該有一個名稱與一個型別,並以冒號(: ) 分隔。在本例中,該方法接受一個型別為 String 的 text 參數。061 03
在該方法中,這是用來將文字轉語音的幾行程式碼,唯一的差異是下列這行程式碼:
let utterance = AVSpeechUtterance(string: text)
我們將字串參數設定為「text」,也就是方法呼叫者所傳送的文字。
既然我們已經建立了方法,那麼該如何呼叫它呢?你只需要使用方法名稱,並將所需的參數傳送給它,如下所示:
speak(text: "Hello World")
我們回到 ContentView 結構來修改程式碼,首先建立如圖 3.21 所示的 speak(text: String) 方法,接著你可以透過呼叫 speak 方法來取代兩個按鈕的動作區塊。
這個新方法不會改變 App 的功能,兩個按鈕的運作與之前完全相同,但是正如你從最終的程式碼中看到的那樣,它更易於閱讀且簡潔。
而且,如果你需要在 App 中傳入另一個文字轉語音按鈕時怎麼辦?你不再需要複製這四行程式碼,只需要呼叫 speak 方法與要讀取的文字即可,這就是為什麼我們需要將常用的操作群組為方法。
你的作業:按鈕與方法的應用
為了幫助你充分了解如何使用按鈕與方法,這裡有一個簡單的作業供你練習,你的任務是修改目前的程式碼並建立「猜猜這些電影」App。這個UI 及其功能和「Hello World」 App 非常相似,每個按鈕都會顯示一組表情符號圖示,而玩家的任務是從這些表情符號中猜出電影的名稱,透過點擊按鈕,App 會說出正確的答案。例如:當玩家點擊藍色按鈕時,App 會唸出「答案是 Ocean 11」,如圖 3.22 所示。
本章小結
恭喜 !你已經建立了你的第一個 iPhone App,這雖然是一個簡單的 App,但是我相信你已經對 Xcode、SwiftUI 與 iOS SDK 提供的內建框架有了更深的了解,這比你想像的還要容易,對吧?
本文摘自《iOS 17 App程式設計實戰心法》(SwiftUI)》一書。如果你想更深入學習Swift程式設計和下載完整程式碼,你可以從 AppCoda網站 購買完整電子版。