還記得前面功能規劃時,我們有提到,快速記帳要在五秒內完成記帳,然後有空的時候,再回頭來整理記帳資料。而錄音就是最快的紀錄方式,我們只要花很短的時間,就可以記錄很詳細的資料。
首先要建立一個錄音按鈕,使用者按住後開始錄音,放開後停止錄音。
let recordButton: UIButton = {
let button = UIButton()
button.setTitle("按住我錄音", for: .normal)
button.setTitleColor(MMColor.white, for: .normal)
button.backgroundColor = MMColor.black
button.layer.cornerRadius = 4
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(startRecording), for: .touchDown)
button.addTarget(self, action: #selector(stopRecording), for: .touchUpInside)
button.addTarget(self, action: #selector(stopRecording), for: .touchUpOutside)
return button
}()
備註:因為放開按鈕有兩種情形,一種是使用者按著按鈕後,手指頭移出按鈕之後才放開,另外一種就是手指頭在按鈕內放開。因此這兩種情形都需要執行同一個動作:「停止錄音」。
接著我們把按鈕加入到畫面中,標籤的下方,如下:
private func addSubviews() {
view.addSubview(tagCollectionView)
// ...略
view.addSubview(recordButton)
recordButton.translatesAutoresizingMaskIntoConstraints = false
recordButton.topAnchor.constraint(equalTo: tagCollectionView.bottomAnchor, constant: 10).isActive = true
recordButton.leftAnchor.constraint(equalTo: view.layoutMarginsGuide.leftAnchor).isActive = true
recordButton.rightAnchor.constraint(equalTo: view.layoutMarginsGuide.rightAnchor).isActive = true
recordButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
}
我們先暫時把錄音檔案存在 APP 的 Document 目錄下,並指定檔名為 recording.m4a,將來我們會需要亂數檔名,並將檔名存在 Core Data(再之後會同步到後端資料庫),使用者之後回來 APP 時可以選擇要聽哪一個錄音。
let documentDirectory: URL? = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
lazy var audioRecorder: AVAudioRecorder? = {
guard let documentDirectory = documentDirectory else {
return nil
}
return try! AVAudioRecorder(url: documentDirectory.appendingPathComponent("recording.m4a"), settings: [
AVFormatIDKey: kAudioFormatMPEG4AAC,
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
])
}()
使用者按著錄音按鈕後,我們就開始一個 AVAudioSession 並請求使用者的麥克風權限後(記得要設定 info.plist),開始錄音,如下:
@objc func startRecording() {
let session = AVAudioSession.sharedInstance()
session.requestRecordPermission { allowed in
if allowed {
do {
try session.setCategory(AVAudioSessionCategoryPlayAndRecord, with: .defaultToSpeaker)
try session.setActive(true)
} catch {
fatalError("failed to start recording")
}
self.audioRecorder?.record()
self.recordButton.backgroundColor = MMColor.red
self.recordButton.setTitleColor(MMColor.white, for: .normal)
self.recordButton.setTitle("放開結束錄音", for: .normal)
}
}
}
然後結束錄音的部分很簡單:
@objc func stopRecording() {
audioRecorder?.stop()
}
最後,最重要的部分,就是我們監控結束錄音的事件,如下:
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if flag {
recordButton.backgroundColor = MMColor.black
recordButton.setTitleColor(MMColor.white, for: .normal)
recordButton.setTitle("按住我錄音", for: .normal)
}
}
為什麼我們不在 stopRecording 直接把按鈕的樣式換回錄音前的樣子呢?
因為系統可以隨時中斷我們的錄音狀態,比方說錄音到一半,有電話進來,就會被中斷,因此我們應該要用 Delegate 的 audioRecorderDidFinishRecording 來處理「停止錄音」的邏輯。
我們可以很簡單的把錄音直接播出來,驗證我們真的有錄音成功。
首先,先建立一個 AVAudioPlayer 物件,以利後續播放使用(這個物件需定義在 ViewController 下面,因為播放是非同步的行為,如果宣告在 audioRecorderDidFinishRecording 裡面,聲音還沒播放,記憶體就會被釋放了!
class QuickCreateViewController: UIViewController {
// ...略
lazy var audioRecorder: AVAudioRecorder? = {
// ...略
}()
var player: AVAudioPlayer?
// ...略
}
然後請 APP 在錄音完後立即播放出來:
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if flag {
recordButton.backgroundColor = MMColor.black
recordButton.setTitleColor(MMColor.white, for: .normal)
recordButton.setTitle("按住我錄音", for: .normal)
guard let documentDirectory = documentDirectory else {
return
}
do {
player = try AVAudioPlayer(contentsOf: documentDirectory.appendingPathComponent("recording.m4a"))
player?.prepareToPlay()
player?.play()
} catch {
fatalError("Cannot play audio")
}
}
}
介面如下:
程式碼:GitHub
接下來我們會先把快速記帳的頁面收尾,然後在首頁用 UITableView 顯示快速記帳的內容,最後再加入 Core Data 的功能,讓「帳」真的被儲存起來。