用 Siri 來歐北供威─任君調整客製的告白說話 APP

◆課堂練習&作業9–1:開不了口的告白─Outlet & Action+用 AVSpeechSynthesizer 講話◆

製作內容:
1.唸出 text field 輸入的文字
2.利用 slider 控制講話的速度、音調
3.利用 label 顯示 slider 的數值
4.加入隨機按鈕用亂數更改速度及音調的Slider(random)
5.利用 segmented control 做出多國語言(if/else)
6.加入暫停 & 繼續講話功能。像音樂APP一樣,按繼續跟暫停時會變更按鈕圖樣,並讓發聲器暫停/繼續說話(if/else)

成品

螢幕錄影畫質很爛耶(嫌棄)

製作記錄

【用 Label 顯示 Slider 數值】

語法上就是拉 Label 跟 Slider 的 Outlet+Slider 的 Action
拉 Slider(動作)的時候 Label(Outlet) 會顯示對應數值

設定 .format: “%.1f” 讓字串只顯示小數點後一位( “%.2f” 則是小數點後兩位…以此類推)

@IBOutlet weak var speedSlider: UISlider!
@IBOutlet weak var speedValueLable: UILabel!
@IBAction func speedRandomClick(_ sender: Any) {
speedValueLable.text = String(format: “%.1f”, speedSlider.value)
}

【筆記】
1. Label 空間要拉長一點才能完整顯示,因為有些數值會有小數點,如果 Label 塞不下後面數字就會變成 1…
2.Slider的最大值最小值要注意,像是速度或音調不會有 0,所以最小值設了 0.1

【收鍵盤】

使用常見的三種方式來收鍵盤,終於不用在模擬器一直按 cmd+K 隱藏啦(有時候忘記上次點隱藏,模擬器都開起來錄影了才發現沒鍵盤超慌的 XD)

// 1.點空白處
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
// 2.按下 Return 鍵
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder();
return true

}
// 3.按下送出(說出口按鈕)
@IBAction func speakButtonClick(_ sender: Any) {
view.endEditing(true)
}

點空白處跟按 Return 鍵收鍵盤語法中的 touchesBegan 跟 textFieldShouldReturn 是原本就內建的 function(查看方法在參考文章原文內有提到,文章放在下方參考資料庫的地方)

後面那串 (_ touches: Set<UITouch>, with event: UIEvent?)(_ textField: UITextField) -> Bool) 則是輸入時跳出的提示(選擇後直接按 Enter 不用輸入內容)

一開始因為沒跳出來傻傻的自己打,後來重輸入一次才發現那是提示啊!

【按下播放鈕變成暫停樣式】

因為可以實踐發聲器的暫停跟繼續,在按鈕上想要直觀一點,做出像音樂APP那樣,按下播放後會變成暫停圖樣並播放音樂、按下暫停會變成播放圖樣並暫停音樂

像這樣

為了好懂,按鈕圖樣切換部分只取一段語法(其他部分同理)

// 講話暫停(按下去圖案變成播放)
else if speakMesseage.isSpeaking == true, speakButton.titleLabel!.text == “ 修但幾咧{
//條件設定:如果發聲器正在說話+按鈕的文字為修但幾咧(暫停)時,則...
speakMesseage.pauseSpeaking(at: AVSpeechBoundary.immediate)
// 暫停播放發聲器
speakButton.setTitle(“ 說出口”, for: UIControl.State.normal)
speakButton.setImage(UIImage(systemName: “play.fill”), for: UIControl.State.normal)
// 把按鈕文字改為「說出口」、圖片改為「play.fill」(SF Symbol)
}

設定本身並不難,只是要注意換裡面的字是用 .setTitle ,但是要當成條件設定時要寫.titleLabel!.text (屬性跟方法的差異)

後面的 for: UIControl.State.normal 則是指將其設定為正常預設狀態的 Button(非正常狀態:例如被選取(Selected)或是無法使用(不勾選 Enabled)的按鈕狀態)

按鈕切換的語法參考文章如下:

再加上條件設定:

  1. 有輸入字串的情況(包在最外面的 if)
  2. 各個按鈕狀態的條件(第一次按下播放時 .isSpeaking 和 .isPaused 都是 false,暫停時 .isPaused 為 true、說話時 .isSpeaking 為 true,並加上按鈕文字幫助判定)

完整的一串如下:

if MesseageTextField.text != nil{// 第一次播放(按下去圖案變成暫停)
if !speakMesseage.isSpeaking, !speakMesseage.isPaused, speakButton.titleLabel!.text == “ 說出口”{
speakMesseage.speak(messeage)
speakButton.setTitle(“ 修但幾咧”, for: UIControl.State.normal)
speakButton.setImage(UIImage(systemName: “pause.fill”), for: UIControl.State.normal)
}
// 講話暫停(按下去圖案變成播放)
else if speakMesseage.isSpeaking == true, speakButton.titleLabel!.text == “ 修但幾咧”{
speakMesseage.pauseSpeaking(at: AVSpeechBoundary.immediate)
speakButton.setTitle(“ 說出口”, for: UIControl.State.normal)
speakButton.setImage(UIImage(systemName: “play.fill”), for: UIControl.State.normal)
}
// 暫停完繼續講(按下去圖案變成暫停)
else if speakMesseage.isPaused == true, speakButton.titleLabel!.text == “ 說出口”{
speakMesseage.continueSpeaking()
speakButton.setTitle(“ 修但幾咧”, for: UIControl.State.normal)
speakButton.setImage(UIImage(systemName: “pause.fill”), for: UIControl.State.normal)
}
}

本來用電腦模擬器看按鈕都能順利切換,孰不知接到真的可以發聲的手機後才是考驗的開始…那就是

我的發聲器會跳針!!!只要按一次說話按鈕就講一次話(而且原本按的不會停下來,會疊在一起)我弟路過還以為我手機壞了😂 因為按下去只會一直說話,後面暫停跟繼續的條件都無法達成 (つд⊂)

音量注意XD 然後會很吵

原因是:發聲器語法中的 let xxx = AVSpeechSynthesizer()(也就是能發聲的程式本體)要寫在外面

原本包含發聲器語法都是寫在 @IBAction 裡面,才會導致一按按鈕就觸發他說話進入無限循環。原本一直以為是條件沒設定好,結果問了彼得潘後恍然大悟(盲點 XD)

另外,按鈕上的文字有空白純粹是為了美觀(不讓字跟 Icon 黏在一起)但是寫語法的時候要注意空格數量有沒有一致,不然會沒辦法順利切換

【安裝到自己手機發出聲音】

因為 XCode 版本是 12 beta 3,沒有聲音(懶得拆掉再安裝,想直接等正式版的懶人)所以要放到手機才能測試聲音,剛好之前也沒實裝過就來用看看。其實出現的都是小問題,估狗一下馬上跑出彼得潘的文章可以解答 XD

  1. iOS版本不符

2. 出現 Signing Team 錯誤

出現啦~~~其實我只是為了要有文章圖片,所以貼一張截圖上來(喂

【完整程式碼@GitHub】

其實實際跑還是會有一些想要調整的小細節(例如:速度跟音調在講話途中無法連動、話說完無法自動跳回未播放狀態的按鈕、沒輸入時按鈕會不能按或出現提示字)雖然有嘗試用一下,但是沒有很順利解決又不是本次重點就先放置了 _(:3 」∠ )_

--

--