iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 9
0

為什麼要錄音?

還記得前面功能規劃時,我們有提到,快速記帳要在五秒內完成記帳,然後有空的時候,再回頭來整理記帳資料。而錄音就是最快的紀錄方式,我們只要花很短的時間,就可以記錄很詳細的資料。

加入錄音按鈕

首先要建立一個錄音按鈕,使用者按住後開始錄音,放開後停止錄音。

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
}

建立 AVAudioRecorder 準備錄音

我們先暫時把錄音檔案存在 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 的功能,讓「帳」真的被儲存起來。


上一篇
Money Mom - 實作標籤輸入 Part ∞
下一篇
Money Mom - 將快速記帳資料顯示於首頁 Part 1
系列文
iOS 三十天上架記帳 APP30

尚未有邦友留言

立即登入留言