開不了口的告白就用AVSpeechSynthesizer說出口
連接起StoryBoard和程式的IBOutlet & IBAction
繼上一篇利用IBOutlet & IBAction建立起元件和程式間的橋樑,做出換圖片的效果,這篇是AVFoundation之AVSpeechSynthesizer的應用篇
本次運用到以下功能:
小小兵說話器
- 點選UIStepper以固定數值調整說話音高、速度
- 滑動UISlider調整說話音量
- 讓UILabel的數值隨UIStepper和UISlider的數值變動
- 點選UIButton念出預設的文字,並包含已設定好的屬性
藍色說話器
- 讓AVSpeechSynthesizer的說話內容為UITextView填入的文字
- 用if else控制UIButton說話的播放、暫停、停止
- 點選UIButton收鍵盤
- 用UISegmentedControl選擇說話語言
- 滑動UISlider時對應的數值顯示在UILabel上
- 用if else控制UISwitch開關的條件
- 利用亂數隨機設定單一/全部說話屬性
- 點選UIButton讓說話屬性回到初始設定
- 利用String Interpolation(字串置換)印出數值
- 學習呼叫拉了IBAction的funciton
元件外觀調整說明
View
我在小小兵說話器和藍色說話器的說話屬性的元件們下面墊了一個View,想用來凸顯調整屬性功能的區塊。然後試了兩種做法來調整View的外觀:
1. 小小兵在StoryBoard新增View元件後,從程式碼自訂function來設定View的圓角、邊框寬度和顏色後,在ViewDidLoad裡呼叫setViewAttribute()
,讓View的外觀在app一啟動就顯現。
@IBOutlet weak var attributeView: UIView!func setViewAttribute(){
attributeView.layer.borderWidth = 5
attributeView.layer.borderColor = UIColor.systemGray4.cgColor
attributeView.layer.cornerRadius = 10
attributeView.clipsToBounds = true
}override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setViewAttribute()}
2. 藍色說話器則是直接在StoryBoard的Identity Inspector的 User Defined Runtime Attributes 幫View設定圓角,這個作法就不用用到viewDidLoad()
。圓角效果同樣要在啟動app後才看的出來。
Switch
在StoryBoard中調整Switch的外觀,Switch的紐和在On狀態下的底色可以透過onTint和ThumbTint設定。
尷尬的是Switch在默認的off狀態下貼在View上會消失。
接著設定Background藍色,Switch卻變成長方形,所以我同樣在Identity Inspector的 User Defined Runtime Attributes 幫background設定和Switch默認相同大小的圓角15,解決了這個問題。
元件初始數值設定
UISlider和UIStepper的初始數值設定
在StoryBoard設定的元件數值(例如value、Maximum、Minimum、Step等)會為元件初始值,如果後續想要隨著event的觸發變更,就要寫在IBAction中。
而UISlider和UIStepper因為設定用來控制相關的聲音屬性,
所以是Maximum、Minimum是依照Utterance屬性的最大、最小值去設定的。
volume:音量,min-max(0–1)
pitchMultiplier:音高,min-max(0.5–2.0)
rate:語速,min-max(0–1.0)
關於AVFoundation的基礎應用可以參考下面文章
學習使用 AVSpeechSynthesizer 講話
小小兵說話器AVFoundation
說話功能相關的四個元件執行IBAction由上至下,每個func裡都用print在debug area顯示數值:
- UIButton : 設定說英國腔後,執行說話speak
- UIStepper : 調整音高 & 音高數值同步顯示在Label上
- UIStepper:調整語速& 語速數值同步顯示在Label上
- UISlider:調整音量& 音量數值同步顯示在Label上
因為四個元件的IBAction裡都有引用到常數sythesizer
和speechUtterence
,
所以生成合成器let sythesizer = AVSpeechSynthesizer()
和說話內容let speechUtterence = AVSpeechUtterance(string: “Bello I love banana “)
要寫在IBAction的外面,如果寫在四個任一元件IBAction內的話,會有讀不到的問題。
import UIKit
import AVFoundationclass ViewController: UIViewController { @IBOutlet weak var attributeView: UIView!
@IBOutlet weak var pitchStepper: UIStepper!
@IBOutlet weak var speedStepper: UIStepper!
@IBOutlet weak var volumeSlider: UISlider!
@IBOutlet weak var pitchValueLabel: UILabel!
@IBOutlet weak var speedValueLabel: UILabel!
@IBOutlet weak var volumeValueLabel: UILabel! let sythesizer = AVSpeechSynthesizer()
let speechUtterence = AVSpeechUtterance(string: "Bello I love banana ")override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setViewAttribute()
}@IBAction func speak(_ sender: UIButton) {
speechUtterence.voice = AVSpeechSynthesisVoice(language: "en-GB")
sythesizer.speak(speechUtterence)
print(pitchStepper.value,speedStepper.value,volumeSlider.value)
}//調整Slider音高數值+Label顯示音高數值@IBAction func adjustPitch(_ sender: UIStepper) {
speechUtterence.pitchMultiplier = Float(pitchStepper.value)
pitchValueLabel.text = String(format: "%.2f", sender.value)
print("pitchStepper",sender.value)
}//調整Slider語速數值+Label顯示語速數值@IBAction func adjustSpeed(_ sender: UIStepper) {
speechUtterence.rate = Float(speedStepper.value)
speedValueLabel.text = String(format: "%.2f", sender.value)
print("rateStepper",sender.value)
}//調整Slider音量數值+Label顯示音量數值 @IBAction func adjustVolume(_ sender: UISlider) {
speechUtterence.volume = Float(volumeSlider.value)
volumeValueLabel.text = String(format: "%.2f", sender.value)
print("volumeSlider",sender.value)
}}
藍色說話器AVFoundation
藍色說話器是微進階版,利用了更多元件來測試功能,並且將每個說話屬性儲存為ViewController的property,避免每次執行function後數值就被清掉。最後在func尾用把數值變字串的方式\( )
print出ViewController的property數值,方便從Xcode的debug area確認每個func是否有跑出正確的結果。
功能設計相較小小兵說話器,藍色說話器為了選擇障礙的夥伴們新增了兩種隨機按鈕:一種針對單一屬性隨機調整UISlider數值、另一種針對全部屬性隨機調整UISlider數值,透過隨機鈕選到想要的數值並將其相對應的UISwitch切換至ON把數值固定住,就不用擔心動到已確認的值,可以放心的隨機選取剩下的屬性數值啦!
如果不想要隨機跑數字時也可以手動調整,與小小兵說話器在UIStepper跟UISlider被觸發後才改變數值不同,藍色說話器是先自訂function,其中包含說話的屬性。
合成器的說話的內容是TextView中輸入的文字,還新增了三種語言選項,在念出中、英,或日文中途可以隨時按暫停和停止。設定好說話屬性後,再執行說話speak。最後再playButton的IBAction裡呼叫自訂的function。
//說話屬性存成ViewController的propertyvar volumeValue = 0.5
var rateValue = 0.5
var pitchValue = 1.0
let synthesizer = AVSpeechSynthesizer()
let language = ["zh-TW","en-US","ja-JP"]
var index = 0//自訂合成器說話屬性funcfunc speak(language:String){
let speechUtterance = AVSpeechUtterance(string: textView.text!)
volumeValue = Double(volumeSlider.value)
rateValue = Double(rateSlider.value)
pitchValue = Double(pitchSlider.value)
speechUtterance.volume = volumeSlider.value
speechUtterance.rate = rateSlider.value
speechUtterance.pitchMultiplier = pitchSlider.value
speechUtterance.voice = AVSpeechSynthesisVoice(language:language)
synthesizer.speak(speechUtterance)}//play按鈕@IBAction func play(_ sender: UIButton) {
if synthesizer.isSpeaking == false{
index = languageSegmentedControl.selectedSegmentIndex
let selectedVoice = language[index]
speak(language: selectedVoice)
}else{
synthesizer.continueSpeaking()
}
print("languageIndex:",index)
print("play volume: \(volumeValue), rate: \(rateValue),pitch: \(pitchValue)")
view.endEditing(true) //點選播放鍵後收鍵盤
}
而三個UISlider的action都是被滑動時左側的Label同步顯示數值,所以可以拉到同一個@IBAction func changeLabelValue
下。
關於Label顯示的格式,”%.2f”
代表顯示小數點後兩位
@IBOutlet weak var volumeSlider: UISlider!
@IBOutlet weak var volumeShuffleButton: UIButton!//滑動全部slider數值時,讓Label數值跟著變動 @IBAction func changeLabelValue(_ sender: Any) {
volumeLabel.text = String(format: "%.2f", volumeSlider.value)
rateLabel.text = String(format: "%.2f", rateSlider.value)
pitchLabel.text = String(format: "%.2f", pitchSlider.value)
}
//針對音量屬性隨機設定slider數值,讓音量Label數值跟著變動
@IBAction func volumeShuffleValue(_ sender: Any) {
volumeValue = Double.random(in: 0...1)
volumeSlider.value = Float(volumeValue)
print("volumeRandom:\(volumeValue)")
volumeLabel.text = String(format:"%.2f",volumeSlider.value) }// 隨機設定全部屬性的slider數值,讓全部Label數值跟著變動
@IBAction func setAllValueRandomly(_ sender: UIButton) {
if volumeSwitch.isOn == false{
self.volumeShuffleValue(self)
}
if rateSwitch.isOn == false{
self.rateShuffleValue(self)
}
if pitchSwitch.isOn == false{
self.pitchShuffleValue(self)
} print("All Randomly set volume:\(volumeValue),rate:\
(rateValue),pitch:\(pitchValue)")
}//switch初始是off,如果on了就無法操作slider和隨機鈕(固定住volumeSlider的值)
@IBAction func volumeIsSwitchedOn(_ sender: UISwitch) {
if sender.isOn{
volumeSlider.isEnabled = false
volumeShuffleButton.isEnabled = false
}else{
volumeSlider.isEnabled = true
volumeShuffleButton.isEnabled = true
}
}
此外,因為在程式前面宣告了class ViewController
屬性,用來存取所有元件func執行過程中的說話屬性數值。
var volumeValue = 0.5
var rateValue = 0.5
var pitchValue = 1.0
let language = ["zh-TW","en-US","ja-JP"]var index = 0
所以在設定resetButton時,直接在他的func中將初始值再儲存給屬性就好。
(之後有機會用struct來定義新的型別後就不用像class要宣告一堆變數看起來落落長又很繁瑣)
除了透過點選resetButton回歸初始值以外,也要讓App一啟動就是初始狀態,所以ViewDidLoad的func裡也要呼叫resetButton的func updateAllValue()。
但因為是拉了IBAction的func不能按一般方式呼叫,但又不想再自訂一個一樣的function執行初始值,所以我找到了呼叫IBAction function的方式:
class ViewController: UIViewController { var volumeValue = 0.5
var rateValue = 0.5
var pitchValue = 1.0
let language = ["zh-TW","en-US","ja-JP"]
var index = 0 let synthesizer = AVSpeechSynthesizer()override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.updateAllValue(self)
}//1. 將全部slider和Label數值回歸初始值
//2. 如果有任何switch是on的狀態,全部回關off (slider數值才能變動)
//3. 讓各slider的隨機鈕回歸可選取狀態
@IBAction func updateAllValue(_ sender: Any) {
volumeValue = 0.5
rateValue = 0.5
pitchValue = 1.0
volumeSlider.value = Float(volumeValue)
rateSlider.value = Float(rateValue)
pitchSlider.value = Float(pitchValue)
volumeLabel.text = String(format: "%.2f", volumeValue)
rateLabel.text = String(format:"%.2f",rateValue)
pitchLabel.text = String(format:"%.2f",pitchValue)
languageSegmentedControl.selectedSegmentIndex = index
if volumeSwitch.isOn{
volumeSwitch.isOn = false
volumeSlider.isEnabled = true
volumeShuffleButton.isEnabled = true
}
if rateSwitch.isOn{
rateSwitch.isOn = false
rateSlider.isEnabled = true
rateShuffleButton.isEnabled = true
}
if pitchSwitch.isOn{
pitchSwitch.isOn = false
pitchSlider.isEnabled = true
pitchShuffleButton.isEnabled = true
}
print("updated volume:\(volumeValue),rate:\(rateValue),pitch:\(pitchValue)")
}
完整程式碼
專案下載
其他小小注意
- 匯入的函式庫
import AVFoundation
要寫在class ViewController: UIViewController
的外面。
→ 因為是整個檔案匯入AVFoundation的函式庫 說話的屬性們
和用合成器念出要說的話
如果寫在同一個func下(藍色說話器的程式寫法),要注意說話的屬性們
一定要寫在用合成器念出要說的話
之前。
→ 如果合成器都講完話了才去設定講話屬性就來不及了!
(當func越寫越多,這點會意外變成盲點要特別留意)
參考文章
String的數值格式
顏色挑選
功能及程式參考