5.%d Xcode — 試寫一個計算機 APP
目錄
⦿ 計算機
⦿ 按下數字鍵
⦿ 按下刪除鍵
⦿ 按下百分號、變號、小數點
⦿ 按下運算符號
⦿ 先跳到 equalsBtnTapped
⦿ 回頭看運算符號
⦿ 按下 AC 鍵
⦿ 改進方向
計算機
在二進位的世界裡,計算機是一個簡單而基本的功能,所以每個接觸程式語言的新手,欲衝破程式語言與真實世界的隔閡,常會以完成計算機 APP 做為一個指標,這裡先上成果圖:
首先,欲呈現結果需要用到一個 Label,其他則都是 Button,按下 Button 後會有相應動作。
Button 分為數字、運算符號、小數點、倒退鍵、全清除,先看到數字 Button 的 trick,如下:
如果所有數字鍵連到同一個 IBAction 中,我們可以用 Tag 來對數字作區分,即是 IBAction 可以知道是哪個 Tag 的數字被按下並反應。
Tag 的初始值為 0,假設 Tag 的初始值為 1,0 ~ 9 這些數字就要用 1 ~ 10 來表示,也就是數字大小為 Tag - 1
。如下:
@IBAction func numbers(_ sender: Any) {
let inputNumber = (sender as AnyObject).tag - 1
}
為什麼 sender 必須先轉型成 AnyObject 後才能取得 tag,這是因為當初 IBAction 沒有設定 sender 的型別為 UIButton,所以它預設就是 Any 了,但 UIElement 都是一個個物件
,所以須先轉型為 AnyObject 才能取得在 Storyboard 中設定的 tag,避免麻煩,我們將之改成如下:
@IBAction func numbers(_ sender: UIButton) {
let inputNumber = sender.tag - 1
}
繼續閱讀|回目錄
按下數字鍵
在你熟悉計算機邏輯時,按下數字鍵後於顯示螢幕會有什麼反應呢?如果它不是 0 與運算符號,我們都希望數字是一個個加上去的,例如 1、2、3,會依序顯示 1、12、123。
於是我們就可以這樣寫:
guard let text = resultLabel.text else { return }
let filterCharacter = ["0", "+", "−", "×", "÷"]
if !filterCharacter.contains(text) {
resultLabel.text! += "\(inputNumber)"
} else {
resultLabel.text = "\(inputNumber)"
}
但還沒結束,如過 inputNumber 沒有限制的話,你的螢幕將會出現 ……,這不是很好看,所以我們限制最終 inputNumber 的顯示在十位數以內,如果計算輸入超過十位數,就先以取十位為主。
當然,這在邏輯上並不太對,如果你輸入 1111111111(十位),加上 8888888889(十位),會等於 10000000000(十一位),卻顯示 1000000000(十位),這並不是真正的結果,一般來說,超過顯示範圍,計算機會以科學記號來顯現,但我們先不要管那麼多,看到如下:
if resultLabel.text!.count >= 10 {
resultLabel.text = String(resultLabel.text!.prefix(10))
}
// 記錄當前數值以做運算
currentNum = Double(resultLabel.text!) ?? 0
最後,我們還須記錄當前螢幕顯示數字以做運算。
繼續閱讀|回目錄
按下刪除鍵
看到如下:
@IBAction func deleteBtn(_ sender: Any) {
// if resultLabel.text != "0" {
// resultLabel.text?.removeLast()
// if resultLabel.text! == "" {
// resultLabel.text = "0"
// }
// }
if resultLabel.text?.count != 1 {
resultLabel.text?.removeLast()
} else {
resultLabel.text = "0"
}
currentNum = Double(resultLabel.text!) ?? 0
}
刪除鍵的邏輯為,如果 Label 只剩下一位數,就將這一位數設為 0,Label 可以為空,但計算機不會有空的情形,所以只要 Label 大於一位數,就移除最後面那一位數,而每段操作都要注意會不會進到運算邏輯中。
若我們希望修改後的數字計算拿去做運算,就要將結果存於 currentNum。
繼續閱讀|回目錄
按下百分號、變號、小數點
首先看到設定 Label 的方法:
private func setupResultLabel(from number: Double) {
var wantedText: String
// 無條件捨去後若與自身相等,如 2.0 == 2.0,則無須顯示小數點,否則直接顯示
if floor(number) == number {
wantedText = "\(Int(number))"
} else {
wantedText = "\(number)"
}
// 並且字串長度限縮為 10
if wantedText.count >= 10 {
wantedText = String(wantedText.prefix(10))
}
resultLabel.text = wantedText
}
這個方法中只有處理邏輯,沒有運算邏輯,在計算機的顯示邏輯主要是結果必須相符於運算,但這並不是說 1.234567890
就得顯示出來,因為前面說到限縮十位數,而包含小數點的 1.234567890 已經是十一位數了,我們需要適時地顯示該顯示的數字即可。
接著,如果運算結果為整數,由於運算時我們使用 Double,所以如果是整數.0
的結果,如 1.0、13.0 呈現在螢幕上相當不好看,所以必須要確定是否有小數,如果沒小數,就轉型為整數,如果有小數,就呈現。
我們看到百分號與變號方法:
@IBAction func percentageBtn(_ sender: Any) {
currentNum = currentNum / 100
setupResultLabel(from: currentNum)
}
@IBAction func signBtn(_ sender: Any) {
currentNum = -currentNum
setupResultLabel(from: currentNum)
}
由於百分號與變號只需將運算結果顯示於螢幕上,所以先將 currentNum 的結果算出來,最後再處理這個數字並顯示。
而按下小數點按鍵,方法如下:
@IBAction func dotBtn(_ sender: Any) {
if !(resultLabel.text?.contains("."))! {
resultLabel.text! += "."
}
}
如果 Label 沒有包含小數點,才能夠加入小數點,否則不進到流程內。
繼續閱讀|回目錄
按下運算符號
假如按下 + 這個 Button,我們會做什麼事,先看到如下:
var previousNum: Double = 0@IBAction func plusBtn(_ sender: Any) {
resultLabel.text = "+"
isCalculated = true
previousNum = currentNum
}
通常按下 + 號,前面會有數字,而在按下數字鍵的錨點,該段落有說到,最後需將結果存到 currentNum 裡,方便以後做運算。
然而按下 + 號,下一步就是準備運算了,如果所有的數字運算都是由 previousNum 與 currentNum 做四則運算、百分號、變號的運算,此時就應該將前次輸入的結果(currentNum)存到 previousNum。
而且螢幕上需出現運算符號。
最初的 currentNum 設為 0,當你按下 + 號,previousNum 原為 0,這時又被設定一次 0,在按下其他數字
時,currentNum 又變成其他數字
,當你按下 equalsBtn 才真正開始運算,最後顯示結果。
所以,我們先跳到 equalsBtnTapped 看看吧!
繼續閱讀|回目錄
先跳到 equalsBtnTapped
看到如下:
@IBAction func equalsBtn(_ sender: Any) {
if isCalculated == true {
switch operactor {
case .plus:
currentNum = previousNum + currentNum
setupResultLabel(from: currentNum)
case .minus:
currentNum = previousNum - currentNum
setupResultLabel(from: currentNum)
case .times:
currentNum = previousNum * currentNum
setupResultLabel(from: currentNum)
case .divide:
currentNum = previousNum / currentNum
setupResultLabel(from: currentNum)
case .none:
resultLabel.text = ""
}
isCalculated = false
}
}
按下 = 號,希望進到流程判斷,如果運算子是 + 號,進行加法運算,接著以此類推,但在流程控制上,希望是該四則運算時做四則運算,這即是說要把四則運算與百分號、變號運算邏輯分開,事實是我們也拆成三個方法了。
而在 switch case 中,我們能夠藉由 operator 去比對 .plus、.minus、.times、.divide、.none,可以猜到用 enum 去寫了一個 OperationType,如下:
enum OperationType {
case plus
case minus
case times
case divide
case none
}
而又 operator: OperationType = .none,所以 case 裡可用不同情境去處理四則運算,又不怕打錯字(因不用 String 去做比對),這個處理就是前面用的 setupResultLabel(from number: Double)
。
我們在保持運算的情況下,按下 = 號才有作用,否則沒作用,而按下等下需要有作用的前提是按下運算子,這也就是為什麼運算子那個 method 中有 isCalculated 要設為 true。
接著回頭看運算符號。
繼續閱讀|回目錄
回頭看運算符號
程式碼如下:
@IBAction func operatorBtnTapped(_ sender: UIButton) {
let operation = sender.currentTitle
resultLabel.text = operation
switch operation {
case "+": operactor = .plus
case "−": operactor = .minus
case "×": operactor = .times
case "÷": operactor = .divide
default: break
}
isCalculated = true
previousNum = currentNum
}
在這裡為了方便,將所有運算符號的 IBAction 放在一起判斷,雖然前面用 enum 減少了打錯字的變因,但這邊仍須比對 sender 的 currentTitle 來將 operator 做相應的 case 設定。
好了!完成了。
繼續閱讀|回目錄
按下 AC 鍵
程式碼如下:
@IBAction func clearBtn(_ sender: Any) {
previousNum = 0
currentNum = 0
isCalculated = false
tempNum = 0
operactor = .none
resultLabel.text = "0"
}
將所有變數及 UI 重置。