WFU

2023年4月6日 星期四

Chapter 02 使用 Playgrounds 來體驗 Swift

讀者:楊于葳




本文為「iOS 12 App 程式開發實務心法:30 個製作專業級 iOS App 完全實戰攻略」這本書的閱讀筆記與實作記錄的目錄,目的是希望幫助想利用 Swift 從頭建立一個自己心目中的 App 的人。以下是「Chapter 02 使用 Playgrounds 來體驗 Swift」的筆記內容。

備注:第四章以前,會以「iOS 12 APP程式開發實務心法」的內容記錄,第四章以後,將用最新版的「快速精通iOS 16程式設計」記錄。




2.1 開始學習Swift

Swift 比 Objective-C 來得更簡潔易懂,每一段敘述結束的地方都沒有@符號和分號。


2.2 在Playgrounds中測試Swift

Playgrounds 是 Xcode 的內建功能,可以實際看到程式運作後的結果。Version 14.2 版本操作過程:點開 Xcode ➜ 最上方工具列的「File」➜ New ➜ Playgrounds ➜ Blank :




即可得到以下畫面:




接下來的幾個章節會介紹:

(1)常數(constant)、變數(variable)與型態推論(type inference)

(2)控制流程(control flow)

(3)集合型態(collection types),像陣列(array)與字典(dictionary)

(4)Optional


2.3 常數與變數


在 Swift 語言中,let 用來宣告常數(constant),表示該變數的值在宣告後不能再被修改,通常用在程式執行期間不會改變的值,例如:圓周率 π 或常數 e 等。var 用來宣告變數(variable),表示該變數的值隨時可以被修改。

let 用來儲存不會變更的值,var 儲存可變更的值。


 x = y + 10


x 和 y 是變數。10是常數,指沒有改變的值。要把上方的方程式寫成 Swift 程式,要先用 var 關鍵字宣告一個變數,另外使用 let 關鍵字宣告一個常數。


import
UIKit


let constant = 10

var y = 10

var x = y + constant


在 Playground 輸入後,會得到以下結果:




除了按 Play 鍵以外,也可以使用快捷鍵 Shift + command + Enter 來執行程式。


 constant = 20
 number = 50 



import
UIKit


let constant = 10

var number = 10

var x = number + constant


contant = 20

number = 50






所以在給一個新的常數時,Xcode 會出現錯誤。當常數以一個值來做初始化,如果初始化後還想變更其值的話,則使用變數。 


2.4 了解型態推論

Swift 提供一項叫「型態推論」的功能,如果我們沒有明確指定型態,編譯器能自動推斷出最合適的型態,幫助開發者撰寫出簡潔的程式。



import UIKit


let constant: Int = 10

var number: Int = 10

var result: Int = number + constant



在 Swift 中,「 let constant: Int 」裡,let 的後面可以接一個識別符(identifier),用來定義常數的名稱,這個名稱可以是任何我們想要的字母、數字和底線的組合,而名稱的第一個字不能是數字。這裡的 constant 指的是常數。

let 和 var 的後面都可以加上「 : 」和特定關鍵字指定型態,而特定關鍵字的名稱是區分大小寫的。例如:「 myConstant 」和「 MyConstant 」是不同的常數。

Int 表示這個變數or常數的型態,是一種整數。 

Double 表示這個變數or常數的型態,是一種小數。 
 
String 表示一串文字。 
 
Bool 意思是布林(boolean)值,表示 true 或 false。

如果要在等號後面呈現小數型態,就要將 number 改成 Double:


var number: Double = 10.5


如果要讓他們相加,需要把 result 也改成 Double 的型態,讓 result 可以呈現小數,結果如下:


import UIKit


let constant: Int = 10

var number: Double = 10.5

var result: Double = number + Double(constant)



這裡的 number 代表 10.5,Double(constant) 代表 10,所以兩者相加,會得到20.5。




在 Playground 裡,可以按住 option 鍵,點選任意一個變數名稱,查看編譯器推論出的變數型態。






2.5 文字的處理

想儲存文字資料,可以使用 String 型態,文字資料以雙引號(")來包圍。


var messsage = "The best way to get started is to stop talking and code."





在 Swift 中,一個變數名稱只能宣告一次,所以輸入 var messsage 後,再次輸入 let messsage,會出現錯誤。


import UIKit


var messsage = "The best way to get started is to stop talking and code."

let messsage = "The best way to get started is to stop talking and code."



如果要讓兩者同時正確顯示,就需要改變其中一個的變數名稱。例如,把 let messsage 的 messsage 改成大寫開頭,變成 let Messsage 。




import UIKit


var greeting = "Hello "

var name = "Vivian"

var message = greeting + name


輸入上述程式碼,可以達到字串串接的效果。要注意的是,如果 “Hello” 後面沒有空一格,最後的結果,就不會出現空格,而是兩個字會毫無間隔的連在一起。




在 Swift 中,可利用不同的運算子與函數操作字串:

uppercased() 可以把字串轉換成大寫。

lowercased() 可以把字串轉換成小寫。

message.count 可以計算一個字串有幾個字元。 


import UIKit


var greeting = "Hello "

var name = "Vivian"

var message = greeting + name


message.uppercased()

message.lowercased()

message.count





(1)實作案例:顯示購買書本的總價


書本單本價格39,共買5本,顯示總價格。


var bookPrice = 39

var numOfCopies = 5

var totalPrice = bookPrice * numOfCopies

var totalPriceMessage = "The price of the book is $" + totalPrice





在這段程式碼輸入後,會出現錯誤,是因為字串和數字不能直接相加。遇到這種狀況,有兩種解決方式:

第一種:從整數型態轉換為文字型態。

var bookPrice = 39

var numOfCopies = 5

var totalPrice = bookPrice * numOfCopies

var totalPriceMessage = "The price of the book is $" + String(totalPrice)



第二種:使用字串插值(string interpolations),建立 totalPriceMessage 變數。


var bookPrice = 39

var numOfCopies = 5

var totalPrice = bookPrice * numOfCopies

var totalPriceMessage = "The price of the book is $ \(totalPrice)"





2.6 流程控制

「關於自信心,我認為你們會意識到,你們的成功不僅僅是因為你們自己所做的一切,而是因為在朋友的幫助下,你們不害怕失敗。如果真的失敗了,爬起來再試一次。如果再次失敗,那就再爬起來,再試一次,如果最後還是失敗了,或許該考慮做些其他的事情,你們能夠站在這裡,不僅是因為你們的成功,而是因為你們不會害怕失敗。」
— John Roberts 美國首席大法官

「如果決定明天要六點起床,你將會為自己煮一頓豐盛的早餐,否則就是到外面去吃早餐」, 這個條件可用程式表示:


var timeYouWakeUp = 6


if timeYouWakeUp == 6 {

    print("Cook yourself a big breakfast!")

} else {

    print("Go out for breakfast.")

}


當中的 = 屬於賦值運算子,== 屬於關係運算子。

關係運算子用於比較兩個值之間的關係,常見的關係運算子包括: >(大於)、<(小於)、>=(大於或等於)、<=(小於或等於)、== (兩個值相等)、 != (不等於)。

 在年底通常會有一筆年終獎金,你將規劃一趟旅行,規劃如下:

  • 如果你獲得10,000的獎金(或者更多),你將決定到巴黎或倫敦旅行。
  • 如果你獲得5000至9999之間的獎金,你將決定到東京旅行。
  • 如果你獲得1000至4999之間的獎金,你將決定到曼谷旅行。
  • 如果獎金少於1000 ,則待在家中。

上述條件可用程式表示成:


var bonus = 5000


if bonus >= 10000 {

    print("I will travel to Paris and London!")

} else if bonus >= 5000 && bonus < 10000{

    print("I will travel to Tokyo.")

} else if bonus >= 1000 && bonus < 5000{

    print("I will travel to Bangkok.")

} else {

    print("Just stay home.")

}



同時要指定兩種條件,可以使用 && 運算子。&& 是一個邏輯運算子(logical operator),代表「且」的意思,當左右兩邊的條件都成立時,結果會回傳 true,反之則為 false。




如果覺得 && 不夠直覺化,可以使用範圍運算子「 ... 」,定義下限到上限的範圍。 


import UIKit


var bonus = 5000


switch bonus {

case 10000...:

    print("I will travel to Paris and London!")

case 5000...9999:

    print("I will travel to Tokyo.")

case 1000...4999:

    print("I will travel to Bangkok.")

default:

    print("Just stay home.")

}



2.7 了解Array與字典

(1)陣列


下標語法(subscript syntax)是一種在物件中使用方括號 [ ] 的語法,可以存取或修改一個物件中的元素,讓開發者更方便對自己定義的物件進行操作。


var bookCollection = ["Tool of Titans","Rework","Your Move"]

bookCollection[0]



使用 bookCollection 後加上方括號 [ ] ,可以存取在 var bookCollection 裡面的數值。第一項為0,第二項為1,第三項為2。


var bookCollection = ["Tool of Titans","Rework","Your Move"]

bookCollection[0]

bookCollection.append("Authority")

bookCollection.count



輸入 bookCollection.append 表示在 bookCollection 裡,新增項目在陣列的最後一個位置。使用 append() 方法新增元素時,一次只能新增一個元素。

輸入 bookCollection.count 可以知道一個陣列的元素個數。


var bookCollection = ["Tool of Titans","Rework","Your Move"]

bookCollection[0]

bookCollection.append("Authority")

bookCollection.count



如果想把陣列每一個元素的值,輸出到主控台上,有一個比較簡潔的做法:


var bookCollection = ["Tool of Titans","Rework","Your Move"]

bookCollection[0]

bookCollection.append("Authority")

bookCollection.count


for index in 0...3 {

    print(bookCollection[index])

}



首先是指定範圍,從第一項到最後一項,也就是 0...3。接著利用迭代(iteration)重複執行一段程式碼,而 for-in 是一種迴圈,用來執行一段程式碼,直到滿足特定的條件才停止,是 Swift 語言中的一個迭代結構。




雖然指定範圍時,寫成 0...3 看起來很簡潔了,但是只要陣列的數量一更改,就必須要修正一次述職,十分不方便,因此可以再利用另一個更簡潔的方式指定範圍:


var bookCollection = ["Tool of Titans","Rework","Your Move"]

bookCollection[0]

bookCollection.append("Authority")

bookCollection.count


for index in 0...bookCollection.count - 1 {

    print(bookCollection[index])

}



使用 0...bookCollection.count - 1 ,就可以減少很多修改的時間,不管陣列項目數量是多少,程式都不會出現錯誤。


for book in bookCollection {

    print(book)

}



Swift 的 for-in 迴圈有另一種迭代方式,當 bookCollection 陣列被迭代後,每一次迭代的項目會被設定給 book 常數。表示在這個 for-in 迴圈中,會依序將 bookCollection 陣列中的每個元素賦值給 book 這個常數,然後執行迴圈內的程式碼。在每次迭代中,都可以使用 book 這個常數來操作當前元素。


(2)字典


字典(dictionary)是另一個常見的集合型態,使用中括號 [ ] 定義,可以讓我們在一個變數或常數中儲存多個值。每個鍵(key)對應到一個值,鍵和值中間用冒號 : 分隔。

如果要讓ISBN作為書本的索引值,可以這樣宣告與初始化一個字典:


import UIKit


var bookCollectionDic = ["1328683788":"Tool of Titans","0307463745":"Rework","1612060919":"Authority"]


bookCollectionDic["0307463745"]


for (key, value) in bookCollectionDic{

    print("ISBN:\(key)")

    print("Title:\(value)")

}



從主控台中的訊息可以知道,項目的排序方式和陣列不一樣,字典沒有依照初始設定的順序,這就是字典的特性,項目是以無排序的方式儲存的。




實作案例:建立一個表情符號字典


在 Mac 上,按下 control + command + space 就會跑出輸入表情符號的虛擬鍵盤。


import UIKit


var emojiDict: [String:String] = ["👻":"Ghost",

                                  "💩":"Popp",

                                  "😤":"Angry",

                                  "😱":"Scream",

                                  "👾":"Alien minster" ]

var wordToLookup = "👻"

var meaning = emojiDict[wordToLookup]


print(meaning)


wordToLookup = "👾"

meaning = emojiDict[wordToLookup]


print(meaning)



var emojiDict: [String:String] 宣告了一個字典變數 emojiDict,它的鍵和值都是 String 類型。可以理解为,emojiDict 是一个包含字符串键和字符串值的字典。

var wordToLookup = "👻" 是指變數的定義,指定值為 "👻",代表著我們要查找的字典中的鍵。

var meaning = emojiDict[wordToLookup] 這行程式碼的意思,是從 emojiDict 字典中查找 wordToLookup 所代表的鍵,並將其對應的值賦值給變數 meaning。以這個例子來說,wordToLookup 的值為 "👻",所以 meaning 的值會被賦為 "Ghost"。

print(meaning) 的作用是將 meaning 變數的值在控制台印出來,也就是將 emojiDict 字典中 wordToLookup 這個 key 所對應的 value 印出來。




(3)陣列和字典的差異


陣列和字典都是在程式設計中常見的資料結構,但在使用上有一些差異:

  • 元素的存取方式不同:陣列可以透過索引(index)來存取元素,而字典則是透過鍵(key)來存取元素。
  • 元素的排序方式不同:陣列是按照元素插入的順序來排序的,而字典則是無序的。
  • 元素的型態不同:陣列的元素可以是同一個型別或不同的型別,而字典的元素通常是同一個型態的鍵值對(key-value pair)。

舉例來說,假設要存儲一些學生的資訊,包括姓名和年齡。我們可以使用陣列來存儲學生姓名,也可以使用字典來存儲學生姓名和年齡的對應關係。如果我們要查找某個學生的年齡,使用字典會更方便,因為可以直接透過學生的姓名查找其年齡,而使用陣列需要透過整個陣列才能找到對應的年齡。


2.8 了解Optionals

為什麼 App 會很常閃退呢?一個比較常見的原因是,在運作期間,App 試著要存取一個沒有直的變數,所以發生了這個例外事件。Swift Optionals 的導入,可以協助程式設計師撰寫更好的程式,以閉眼App閃退的發生。

Optionals 的基礎觀念十分簡單,在存取一個沒有值的變數之前,Swift 會建議我們先做個檢查,我們必須要先確認它有存在一個值,才能繼續,如此就能避免閃退的發生。

到目前為止,我們建立的變數或常數都有一個初始值,初始值在 Swift 裡是必要的。一個非 Optional 的變數,一定要有一個值。

技術上,Optional 只是 Swift 的其中一個型態,讓一個變數可以有指定的值,或者沒有值。如果要宣告一個變數為 Optional,可以在變數後面加上問號。


var jobTitle: String?



在 playgrounds 輸入這行程式碼,按下play按鈕後,右側的面板上會出現 nil 這個字。在程式設計中,nil 是一個表示「沒有值」的特殊常數。當變數或常數沒有被初始化或設置為 nil 時,讀取該變數或常數的值就會返回 nil。

在 Swift 語言中,nil 可以被用於任何可選值 (Optional Value) 的型態中,表示這個可選值目前沒有任何值。如果輸入下面這行,Xcode 會顯示一個錯誤的訊息,這是因為 jobTitle 是沒有值的。


var message = "Your job title is" + jobTitle



每當我們需要存取一個 Optional 變數,Xcode 就會強迫執行確認 Optional 是否有值,這就是 Optionals 避免我們寫出有問題的程式的方法。




因為剛才 print(meaning) 時,沒有給定一個值,所以會出現 Optional 的訊息,現在重新使用 if let,就可以解決這個問題。

if let 在 Swift 語言中,
是一種對 Optional 變數強制解開(forced unwrapping)的方式,以確保其值存在。它可以在 Optional 變數有值時,將其解開unwrap)並綁定到一個新的變數中,以便在 if 語句塊中使用。如果 Optional 變數為 nil,則 if 語句塊中的代碼將被跳過。這樣可以避免因為 nil 值造成的錯誤。

當 meaning 不為 nil 時,將其解包並指定給一個新的變數 let meaning,進而執行大括號內的程式碼。
如果 meaning 為 nil,則不會進入該程式碼。當 meaning 為非 nil 值時,會印出 meaning 的值。


2.9 玩玩UI

iOS SDK 指的是 iOS 軟體開發工具包(Software Development Kit),是蘋果公司提供的一套軟體開發工具,包含函式庫、應用程式程式介面(API)和範例程式碼等資源,幫助開發者快速地開發iOS應用。


import UIKit


var emojiDict = ["👻": "Ghost", "🤖": "Robot", "😤": "Angry", "🤓": "Nerdy", "👾": "Alien monster"]

var wordToLookup = "🤓"

var meaning = emojiDict[wordToLookup]


let containerView = UIView(frame: CGRect(x: 0, y: 0, width: 300, height: 300))

containerView.backgroundColor = UIColor.orange


let emojiLabel = UILabel(frame: CGRect(x: 95, y: 20, width: 150, height: 150))

emojiLabel.text = wordToLookup

emojiLabel.font = UIFont.systemFont(ofSize: 100.0)


containerView.addSubview(emojiLabel)


let meaningLabel = UILabel(frame: CGRect(x: 110, y: 100, width: 150, height: 150))

meaningLabel.text = meaning

meaningLabel.font = UIFont.systemFont(ofSize: 30.0)

meaningLabel.textColor = UIColor.white


containerView.addSubview(meaningLabel)


var meaning = emojiDict[wordToLookup] 是指從 emojiDict 字典中,用 wordToLookup 這個鍵(key)去搜尋對應的值(value),並將結果存入 meaning 變數中。如果找不到對應的值,則 meaning 會被設為 nil。

UIView 是 iOS SDK 中的一個類別,用於創建和管理iOS應用程序中的基本界面元素。它是一個可見的矩形區域,可以包含其他視圖,例如圖像、標籤、文本和其他元素,也可以對用戶事件做出反應,例如觸摸和手勢。通常,iOS 應用程序的界面是使用多個 UIView 組成的,每個UIView都可以包含其他UIView。

UILabel 是 iOS SDK 中的一個類別,用於在iOS應用程式中顯示單行或多行文本。可以在 UILabel 中設置文本的字體、顏色、對齊方式等屬性,以便於開發人員自定義顯示效果。

CGRect 是一個用於定義視圖框架(frame)或矩形的結構,通常用於定位和設置視圖的位置和大小。

使用 .addSubview ,可添加一個圖像視圖作為子視圖,以顯示圖像。

表情符號只是一個字元。可以使用標籤(label)顯示文字。




2.10 下一章的課程:建立第一個App

建立第一個 App 之前,建議要看一下Apple官方的程式語言指南,了解函數(function)、Optionals,以及其他內容。