swiftPractice[20]_終極密碼

Tania
彼得潘的 Swift iOS / Flutter App 開發教室
16 min readDec 13, 2023
練習項目:
1. 產生隨機數字、型別轉換
2. AVSpeechSynthesizer( )講話功能
3. if else 、if let
4. 頁面間資料傳遞、PerformSegue

🎉 歡慶作業 20 🎉 原地歡呼 20 秒!第 20 個練習獻給終極密碼,作業產出前剛好上到資料傳遞,於是通通加進去,不只花費時間倍增外加大卡關一波~看到這裡表示我成功破關逃出了啦!!😤

APP 功能

1. 正確答案為隨機產生的數字
2. 判斷玩家答案:答錯顯示區間、TextField 數字清空
3. 自動念出數字範圍
4. 有 6 次機會,並於畫面顯示計算
5. 6 次機會用盡,跳到挑戰失敗頁面並顯示答案
6. 「 再玩一次 」 按鍵可重來
7. 按空白處收鍵盤

拆解!

拆解目錄:
1. 創建兩個ViewController
2. 拉IBOutlet & IBAction
3. 設定初始畫面
4. 說話func:唸出數字範圍
5. 功能一、輸入答案,檢查結果的Button
5-1. 更新生命值
5-2. 使用if let檢查TextField有無值
5-3. 用程式控制畫面跳轉:PerformSegue
6. 功能二、再玩一次Button
7. 功能三、點空白處收鍵盤
8. 使用IBSegueAction傳遞答案到下一頁

1. 創建兩個 ViewController

✦ 從File > New > File 創建兩個 Cocoa Touch Class 檔,繼承 UIViewController

2. 拉IBOutlet & IBAction

 //guessNumberViewController
@IBOutlet weak var goButton: UIButton!
@IBOutlet weak var rangeLabel: UILabel!
@IBOutlet weak var numberTextField: UITextField!
@IBOutlet weak var lifeLabel: UILabel!

//resultViewController
@IBOutlet weak var resultLabel: UILabel!

3. 設定初始畫面

✦ 在 viewDidLoad 裡面寫 APP 開啟畫面要呈現的資訊、細節

//宣告各種初始變數
var computerNumber:Int = 0
var playerNumber:Int = 0
var maxNumber:Int = 100
var minNumber:Int = 0
var life:Int = 6
override func viewDidLoad() {
super.viewDidLoad()
//調整go按鈕顏色
goButton.configuration?.baseBackgroundColor = UIColor(red: 148/255, green: 17/255, blue: 0, alpha: 1)
goButton.isUserInteractionEnabled = true
//設定TextField圓角
numberTextField.layer.cornerRadius = 10
//設定Label文字
rangeLabel.text = "0~100 Guess a number!"
//speak功能念出Label內容
speak(rangeText: rangeLabel.text!)
//在0~100範圍內隨機出題
computerNumber = Int.random(in: 0...100)
//宣告最大、最小、生命值
maxNumber = 100
minNumber = 0
life = 6
//設定TextField為空字串
numberTextField.text = ""
//設定lifeLabel內容
lifeLabel.text = "🖤 6"
}

4. 說話 func : 唸出數字範圍

✦ 為了達到類似遊戲旁白的效果,我把說話做成一個功能 speak,需傳入型別為字串的參數,也就是 rangeLabel 裡的內容

✦ 先宣告 AVSpeechSynthesizer( )
✦ 利用 AVSpeechUtterance 調整音調、語速、特殊聲音後,再傳入 AVSpeechSynthesizer 的 speak 功能讓程式說話

let synthesizer = AVSpeechSynthesizer()
//唸出範圍的說話功能
func speak(rangeText:String){
let utterance = AVSpeechUtterance(string: rangeText)
//音調:介於0.5~2之間
utterance.pitchMultiplier = 0.5
//語速
utterance.rate = 0.5
//特殊聲音
utterance.voice = AVSpeechSynthesisVoice(identifier: "com.apple.speech.synthesis.voice.Zarvox")
synthesizer.speak(utterance)
}

5. 功能一、輸入答案,檢查結果的Button

✦ 將重複指令整合寫成 updateUI( ):每次輸入後清除框框內的文字,並使用前面寫好的 speak 功能念出rangeLabel的內容

✦ 檢查一:判斷數值是否超過範圍,如果數值 「 ≥ 最大值 」 或 「 ≤ 最小值 」 判斷成立
✦ 檢查二:當數值大於答案,更新最大值為該數值
✦ 檢查三:當數值小於答案,更新最小值為該數值
✦ 檢查四:若以上都不成立代表猜對,答對後需限制繼續輸入數字

func updateUI(){
numberTextField.text = ""
speak(rangeText: rangeLabel.text!)
}
   //輸入答案、檢查結果按鈕
@IBAction func tapShowResult(_ sender: UIButton) {


(...前面省略...)


//判斷玩家數字大小區間、答對與否
if playerNumber >= maxNumber || playerNumber <= minNumber{
rangeLabel.text = "Out of range!! \(minNumber)~\(maxNumber)!"
updateUI()

}else if playerNumber > computerNumber{
maxNumber = playerNumber
rangeLabel.text = "\(minNumber)~\(maxNumber)"
updateUI()

}else if playerNumber < computerNumber{
minNumber = playerNumber
rangeLabel.text = "\(minNumber)~\(maxNumber)"
updateUI()

}else{
rangeLabel.text = "Bang! It is \(computerNumber)!!"
updateUI()
//答對後限制繼續輸入數字
goButton.isUserInteractionEnabled = false
}

(...後面省略...)

}

5–1. 更新生命值

✦ 每按一次 Go,代表作答一次,就會少一次機會所以 life = life - 1 ,再將結果存入 lifeLabel

life -= 1
lifeLabel.text = "🖤 \(life)"

5–2. 使用 if let 檢查 TextField 有無值

✦ 如果使用者有輸入數字,在轉換成 Int 時才不會失敗變成 nil,也才能進行後續的大小判斷,並給出範圍提示

✦ 如果判斷無值,顯示 ”Enter a number!” 提醒(恐嚇?)使用者輸入,並使用事先寫好的說話功能念出字串

//檢查textfield是否有值
if let number = Int(numberTextField.text!){
playerNumber = number


(...中間省略...)


//if let檢查的結果如果是無值
}else{
rangeLabel.text = "Enter a number!"
speak(rangeText: rangeLabel.text!)
}
}

5–3. 用程式控制畫面跳轉:PerformSegue

✦ 我把這個條件放在最上面,按下 Button 時程式將檢查,如果 life == 0 而且使用者數字不等於正確答案時,會切換到下一個設置好的頁面

✦ segue 的 Identifier 要記得設定!

✦ 如果輸了,將 goButton.isUserInteractionEnabled = false,限制繼續答題

//六次還是猜不到,跳到結果頁面
if life == 0, playerNumber != computerNumber{
performSegue(withIdentifier: "resultPage", sender: nil)
goButton.isUserInteractionEnabled = false
}
設定 segue 的 Identifier

6. 功能二、再玩一次 Button

✦ 再次呼叫 viewDidLoad( ),讓畫面還原到初始畫面

//再玩一次按紐
@IBAction func restart(_ sender: UIButton) {
viewDidLoad()
}

7. 功能三、點空白處收鍵盤

✦ 將 Tap Gesture Recognizer 加到 view 上,呼叫.endEditing(true),每當點到 view 即代表完成編輯,鍵盤收起,其他方法可參考下面連結👇

//點空白處收鍵盤
@IBAction func closeKeyboard(_ sender: Any) {
view.endEditing(true)
}

8. 使用 IBSegueAction 傳遞答案到下一頁

✦ 把答案 computerNumber 傳到下一頁宣告好的變數 result 中

    //傳遞computerNumber到下一頁
@IBSegueAction func showResultPage(_ coder: NSCoder) -> ResultViewController? {
let controller = ResultViewController(coder: coder)
controller?.result = computerNumber
return controller
}

完整程式碼

GuessNumberViewController

import UIKit
import AVFAudio

class GuessNumberViewController: UIViewController {

@IBOutlet weak var goButton: UIButton!
@IBOutlet weak var rangeLabel: UILabel!
@IBOutlet weak var numberTextField: UITextField!
@IBOutlet weak var lifeLabel: UILabel!

let synthesizer = AVSpeechSynthesizer()
var computerNumber:Int = 0
var playerNumber:Int = 0
var maxNumber:Int = 100
var minNumber:Int = 0
var life:Int = 6

//設定初始畫面
override func viewDidLoad() {
super.viewDidLoad()
goButton.configuration?.baseBackgroundColor = UIColor(red: 148/255, green: 17/255, blue: 0, alpha: 1)
goButton.isUserInteractionEnabled = true
numberTextField.layer.cornerRadius = 10
rangeLabel.text = "0~100 Guess a number!"
speak(rangeText: rangeLabel.text!)
computerNumber = Int.random(in: 0...100)
maxNumber = 100
minNumber = 0
life = 6
numberTextField.text = ""
lifeLabel.text = "🖤 6"
}

//唸出範圍的說話功能
func speak(rangeText:String){
let utterance = AVSpeechUtterance(string: rangeText)
utterance.pitchMultiplier = 0.5
utterance.rate = 0.5
utterance.voice = AVSpeechSynthesisVoice(identifier: "com.apple.speech.synthesis.voice.Zarvox")
synthesizer.speak(utterance)
}

func updateUI(){
numberTextField.text = ""
speak(rangeText: rangeLabel.text!)
}

//輸入答案、檢查結果按鈕
@IBAction func tapShowResult(_ sender: UIButton) {
life -= 1
lifeLabel.text = "🖤 \(life)"

//檢查textfield是否有值
if let number = Int(numberTextField.text!){
playerNumber = number

//六次還是猜不到,跳到結果頁面
if life == 0, playerNumber != computerNumber{
performSegue(withIdentifier: "resultPage", sender: nil)
goButton.isUserInteractionEnabled = false
}

//判斷玩家數字大小區間、答對與否
if playerNumber >= maxNumber || playerNumber <= minNumber{
rangeLabel.text = "Out of range!! \(minNumber)~\(maxNumber)!"
updateUI()
}else if playerNumber > computerNumber{
maxNumber = playerNumber
rangeLabel.text = "\(minNumber)~\(maxNumber)"
updateUI()
}else if playerNumber < computerNumber{
minNumber = playerNumber
rangeLabel.text = "\(minNumber)~\(maxNumber)"
updateUI()
}else{
rangeLabel.text = "Bang! It is \(computerNumber)!!"
updateUI()
goButton.isUserInteractionEnabled = false
}

//if let檢查的結果如果是無值
}else{
rangeLabel.text = "Enter a number!"
speak(rangeText: rangeLabel.text!)
}
}

//再玩一次按紐
@IBAction func restart(_ sender: UIButton) {
viewDidLoad()
}
//點空白處收鍵盤
@IBAction func closeKeyboard(_ sender: Any) {
view.endEditing(true)
}

//傳遞computerNumber到下一頁
@IBSegueAction func showResultPage(_ coder: NSCoder) -> ResultViewController? {
let controller = ResultViewController(coder: coder)
controller?.result = computerNumber
return controller
}
}

ResultViewController

import UIKit

class ResultViewController: UIViewController {
var result:Int!

@IBOutlet weak var resultLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()
resultLabel.text = "It's \(result!)"
}
}

成果:

有聲音的Demo

後記:

SpeechSynthesizer 在模擬器終於有聲音了!前陣子的版本一直會有 “Unable to list voice folder” 的問題,只能在實機上聽到聲音。

這個作業是各種大雜燴功能練習,因為近期上到 protocol 資訊量偏大,光是複習時間就不夠用了,只好把各種功能放在一起練🫠 加油啊啊~~

✦ 寫到尾聲還是被我找到了一個 BUG:如果六次都輸入空字串,就可以繼續作答,不會被判輸喔😂 列入待處理!!

作業出處:

GitHub:

--

--

Tania
彼得潘的 Swift iOS / Flutter App 開發教室

A barista's journey transitioning into iOS development, documenting projects and learning experiences in a dedicated blog. 朝{ 咖啡師+工程師 } 努力中