swiftPractice[23]_打字說話 APP

練習:AVSpeechSynthesizer, UISlider, Delegate

Tania
彼得潘的 Swift iOS / Flutter App 開發教室
18 min readJan 17, 2024

--

顧名思義!懶得說話的時候專用的簡易APP!

APP 功能

● 唸出 textView 輸入的文字
● 利用 slider 控制講話的速度、音調、音量
● 利用 label 顯示 slider 的數值
● 加入兩種講話的 segments
● 重置設定的按鈕
● 自由選擇背景顏色

拆解!

<拆解目錄>
1.畫面規劃、拉 IBOutlet & IBAction
2.設定初始畫面
3.IBAction 功能一:speak(_ sender: UITapGestureRecognizer)
• AVSpeechSynthesizer、AVSpeechUtterance
• 選擇語言的 UISegmentedControl
• 在說完話後執行任務的 AVSpeechSynthesizerDelegate

4.IBAction 功能二:adjustBackground(_ sender: UIButton)
5.IBAction 功能三:adjustSlider(_ sender: UISlider)
• 更新顯示數值 func updateUI()

6.IBAction 功能四:reset(_ sender: UIButton)
7.按空白處收鍵盤

1. 畫面規劃、拉 IBOutlet & IBAction

@IBOutlet weak var speakTextView: UITextView!
@IBOutlet weak var languageSegments: UISegmentedControl!
@IBOutlet weak var speakImageView: UIImageView!

@IBOutlet weak var speedSlider: UISlider!
@IBOutlet weak var pitchSlider: UISlider!
@IBOutlet weak var volumeSlider: UISlider!

@IBOutlet weak var speedLabel: UILabel!
@IBOutlet weak var pitchLabel: UILabel!
@IBOutlet weak var volumeLabel: UILabel!

2. 設定初始畫面

設定介面初始狀態、各Slider區間與預設值,並更新數值 Label

    //設置起始畫面
override func viewDidLoad() {
super.viewDidLoad()
speakTextView.text = ""
//設定TextView圓角
speakTextView.layer.cornerRadius = 10
//隱藏表示說話狀態的Icon
speakImageView.isHidden = true
//背景顏色
view.backgroundColor = UIColor(red: 238/255, green: 222/255, blue: 173/255, alpha: 1)

//設定各Slider區間與預設值
speedSlider.minimumValue = 0
speedSlider.maximumValue = 1
speedSlider.value = 0.5

pitchSlider.minimumValue = 0.5
pitchSlider.maximumValue = 2
pitchSlider.value = 1

volumeSlider.minimumValue = 0
volumeSlider.maximumValue = 1
volumeSlider.value = 1

updateUI()
}

✽ 發現:UITextField 有的 PlaceHolder 功能,UITextView 沒有。

3.IBAction 功能一:speak(_ sender: UITapGestureRecognizer)

將 UITapGestureRecognizer 加到狗狗圖上,點按狗狗可以念出輸入框的文字、並顯示代表說話中的 Icon 動畫。

說話中的 Icon
<功能一完整程式碼>
@IBAction func speak(_ sender: UITapGestureRecognizer) {
//顯示表示說話狀態的Icon
speakImageView.isHidden = false
//設置說話內容、速度、音調、音量
let utterance = AVSpeechUtterance(string: speakTextView.text)
utterance.rate = speedSlider.value
utterance.pitchMultiplier = pitchSlider.value
utterance.volume = volumeSlider.value
//判斷語言Segments,預設值為中文
switch languageSegments.selectedSegmentIndex{
case 0: utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW")
case 1: utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
default: utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW")
}
//設置代理人
synthesizer.delegate = self
synthesizer.speak(utterance)
}

• AVSpeechSynthesizer、AVSpeechUtterance

先在 function 外宣告合成器,避免來不及在 function 跑完前講完話的狀況

let synthesizer = AVSpeechSynthesizer()

在 AVSpeechUtterance 輸入 TextView 的內容,再將各個 Slider 得到的數值分別存入 rate(速度)、pitchMultiplier(音調)、volume(音量),最後在將 utterance 傳入合成器的 .speak( ) 中,就可以開始說話了!


//設置說話內容
let utterance = AVSpeechUtterance(string: speakTextView.text)
//速度
utterance.rate = speedSlider.value
//音調
utterance.pitchMultiplier = pitchSlider.value
//音量
utterance.volume = volumeSlider.value

synthesizer.speak(utterance)

• 選擇語言的 UISegmentedControl

utterance 有另一個 .voice 的功能用來設定說話語言,可以用 SegmentedControl 搭配 switch case 分別設定每個 segment 對應到的設定。

//判斷語言Segments,預設值為中文
switch languageSegments.selectedSegmentIndex{
//繁體中文
case 0: utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW")
//英文
case 1: utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
default: utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW")
}

• 在說完話後執行任務的 AVSpeechSynthesizerDelegate

為了讓動畫能在說話的時候才顯現,我們可以透過 AVSpeechSynthesizerDelegate 的 didFinish功能設定說話結束後移除動畫。

  1. 首先要讓 Controller 遵從 Delegate
    ✽ 利用 extension 將需要定義的功能獨立出來,可以減少 Controller 裝太多東西變得雜亂!
extension TalkViewController:AVSpeechSynthesizerDelegate{
}

2. 設定合成器的代理人是 self, 也就是 TalkViewController

synthesizer.delegate = self

3. 在 didFinish 的功能裡設定說完話後,將說話 Icon 隱藏

 func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
speakImageView.isHidden = true
}

4. IBAction 功能二:adjustBackground(_ sender: UIButton)

設置一個按鈕連接到 UIColorPickerViewController,讓使用者直接選擇背景顏色。

選顏色Demo

使用 present 將 Controller 顯示出來。

<功能二完整程式碼>
//調整背景顏色功能
@IBAction func adjustBackground(_ sender: UIButton) {
//宣告生成一個選顏色的controller
let controller = UIColorPickerViewController()
//設置代理人
controller.delegate = self
//顯示controller
present(controller, animated: true)
}

★★為了知道使用者選了什麼顏色,並在選完後設定背景,我們需要借用UIColorPickerViewControllerDelegate 裡 didSelect 的功能。

  1. 先讓 TalkViewController 遵從 Delegate
extension TalkViewController: UIColorPickerViewControllerDelegate{    

}

2. 設置 UIColorPickerViewController 的代理人為 self,也就是 TalkViewController

controller.delegate = self

3. 在有 didSelect 的功能裡設定背景顏色為 .selectedColor(被選擇的顏色)

func colorPickerViewController(_ viewController: UIColorPickerViewController, didSelect color: UIColor, continuously: Bool) {
view.backgroundColor = viewController.selectedColor
}

5. IBAction 功能三:adjustSlider(_ sender: UISlider)

將三個 Slider 拉進同一個 IBAction ,目的為偵測使用者是否有調整,另外設置一個 function 來更新數值到對應的 Label 中。

//偵測Slider變動
@IBAction func adjustSlider(_ sender: UISlider) {
updateUI()
}

• 更新顯示數值 func updateUI( )

使用 「 %.1f 」 指定取數值到小數點後一位,並轉為字串存入對應的 Label。

//更新Label數值
func updateUI(){
let speedValue = speedSlider.value
let pitchValue = pitchSlider.value
let volumeValue = volumeSlider.value

speedLabel.text = String(format: "%.1f", speedValue)
pitchLabel.text = String(format: "%.1f", pitchValue)
volumeLabel.text = String(format: "%.1f", volumeValue)
}

6. IBAction 功能四:reset(_ sender: UIButton)

重置按鈕,讓畫面回到初始畫面

//重設數值功能
@IBAction func reset(_ sender: UIButton) {
viewDidLoad()
}

7. 按空白處收鍵盤

按空白處收鍵盤

呼叫 touchesEnded 並設定 .endEditing( ) 為 true 來關閉鍵盤。

//按空白處收鍵盤
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}

APP Demo(有聲音)

speakAppDemo

完整程式碼

import UIKit
import AVFoundation

class TalkViewController: UIViewController{
//宣告聲音合成器
let synthesizer = AVSpeechSynthesizer()

@IBOutlet weak var speakTextView: UITextView!
@IBOutlet weak var languageSegments: UISegmentedControl!
@IBOutlet weak var speakImageView: UIImageView!

@IBOutlet weak var speedSlider: UISlider!
@IBOutlet weak var pitchSlider: UISlider!
@IBOutlet weak var volumeSlider: UISlider!

@IBOutlet weak var speedLabel: UILabel!
@IBOutlet weak var pitchLabel: UILabel!
@IBOutlet weak var volumeLabel: UILabel!
//設置起始畫面
override func viewDidLoad() {
super.viewDidLoad()
speakTextView.text = ""
speakTextView.layer.cornerRadius = 10
//隱藏表示說話狀態的Icon
speakImageView.isHidden = true
//背景顏色
view.backgroundColor = UIColor(red: 238/255, green: 222/255, blue: 173/255, alpha: 1)

//設定各Slider區間與預設值
speedSlider.minimumValue = 0
speedSlider.maximumValue = 1
speedSlider.value = 0.5

pitchSlider.minimumValue = 0.5
pitchSlider.maximumValue = 2
pitchSlider.value = 1

volumeSlider.minimumValue = 0
volumeSlider.maximumValue = 1
volumeSlider.value = 1

updateUI()
}
//更新Label數值
func updateUI(){
let speedValue = speedSlider.value
let pitchValue = pitchSlider.value
let volumeValue = volumeSlider.value

speedLabel.text = String(format: "%.1f", speedValue)
pitchLabel.text = String(format: "%.1f", pitchValue)
volumeLabel.text = String(format: "%.1f", volumeValue)
}
//按空白處收鍵盤
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
view.endEditing(true)
}
//點狗狗開始說話功能
@IBAction func speak(_ sender: UITapGestureRecognizer) {
//顯示表示說話狀態的Icon
speakImageView.isHidden = false
//設置說話內容、速度、音調、音量
let utterance = AVSpeechUtterance(string: speakTextView.text)
utterance.rate = speedSlider.value
utterance.pitchMultiplier = pitchSlider.value
utterance.volume = volumeSlider.value
//判斷語言Segments,預設值為中文
switch languageSegments.selectedSegmentIndex{
case 0: utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW")
case 1: utterance.voice = AVSpeechSynthesisVoice(language: "en-US")
default: utterance.voice = AVSpeechSynthesisVoice(language: "zh-TW")
}
//設置代理人
synthesizer.delegate = self
synthesizer.speak(utterance)
}
//調整背景顏色功能
@IBAction func adjustBackground(_ sender: UIButton) {
//宣告生成一個選顏色的controller
let controller = UIColorPickerViewController()
//設置代理人
controller.delegate = self
//顯示controller
present(controller, animated: true)
}
//偵測Slider變動
@IBAction func adjustSlider(_ sender: UISlider) {
updateUI()
}
//重設數值功能
@IBAction func reset(_ sender: UIButton) {
viewDidLoad()
}
}

//遵從需要的Protocol、定義Function
extension TalkViewController: UIColorPickerViewControllerDelegate, AVSpeechSynthesizerDelegate{

func colorPickerViewController(_ viewController: UIColorPickerViewController, didSelect color: UIColor, continuously: Bool) {
view.backgroundColor = viewController.selectedColor
}
func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) {
speakImageView.isHidden = true
}
}

後記

藉輕鬆可愛的小作業練習 protocol、delegate、extension 大魔王😊頓時也變得沒那麼兇猛了~(?下一題!

作業出處

GitHub

--

--

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

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