I get an error in my debug window as per this post's title. I've looked around to try and understand what causes this, but haven't found an explaination, other than loosely related mySQL program errors about called methods not being able to use null (or zero) values. I couldn't see how this is realted to my swift program. I've had this app working when the user controls the length of the recording by tapping 'record', then 'stop resording'. I need to restrict the length of recordibng to 1 second, so I'm using the AVAudioRecorder class' record(forDuration:) function, setting the duration for 1 second.
I've only made this change, (lines 114 - 119), where the recording is now on a 1 second timer instead of waiting for the user to tap 'stop recording'. Now I get the error mentioned at the start of this post.
Even if you can't see the problem with my code, you'll be a great help if you know what the meaning behind the debug output is!
import UIKit
import AVFoundation
class RecordNoteViewController: UIViewController, AVAudioRecorderDelegate {
var stackView: UIStackView!
var recordNoteButton: UIButton!
var playSampleButton: UIButton!
var recordNoteSession: AVAudioSession!
var noteSampler: AVAudioRecorder!
var recordSampleResult: Bool = false
var notePlayer: AVAudioPlayer!
var selectedNote: String = ""
var timer = Timer()
/
override func loadView() {
super.loadView()
view.backgroundColor = UIColor.gray
stackView = UIStackView()
stackView.spacing = 30
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.distribution = UIStackViewDistribution.fillEqually
stackView.alignment = .center
stackView.axis = .vertical
view.addSubview(stackView)
stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Record \(selectedNote)"
navigationItem.backBarButtonItem = UIBarButtonItem(title: "Record Note", style: .plain, target: nil, action: nil)
recordNoteSession = AVAudioSession.sharedInstance()
do {
try recordNoteSession.setCategory(AVAudioSessionCategoryPlayAndRecord)
try recordNoteSession.setActive(true)
recordNoteSession.requestRecordPermission() { [unowned self] allowed in
DispatchQueue.main.async {
if allowed {
self.loadRecordingUI()
} else {
self.loadFailUI()
}
}
}
} catch {
self.loadFailUI()
}
}
func loadRecordingUI() {
recordNoteButton = UIButton()
recordNoteButton.translatesAutoresizingMaskIntoConstraints = false
recordNoteButton.setTitle("Tap to Record", for: .normal)
recordNoteButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .title1)
recordNoteButton.addTarget(self, action: #selector(recordTapped), for: .touchUpInside)
stackView.addArrangedSubview(recordNoteButton)
playSampleButton = UIButton()
playSampleButton.translatesAutoresizingMaskIntoConstraints = false
playSampleButton.setTitle("Tap to Play", for: .normal)
playSampleButton.isHidden = true
playSampleButton.alpha = 0
playSampleButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .title1)
playSampleButton.addTarget(self, action: #selector(playSampleTapped), for: .touchUpInside)
stackView.addArrangedSubview(playSampleButton)
}
func loadFailUI() {
let failLabel = UILabel()
failLabel.font = UIFont.preferredFont(forTextStyle: .headline)
failLabel.text = "Record failed: please ensure the app has access to your microphone."
failLabel.numberOfLines = 0
stackView.addArrangedSubview(failLabel)
}
class func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
class func getNoteSampleURL() -> URL {
return getDocumentsDirectory().appendingPathComponent("note.m4a")
}
func startRecording() {
view.backgroundColor = UIColor(red: 0.6, green: 0, blue: 0, alpha: 1)
recordNoteButton.isEnabled = false
recordNoteButton.setTitle("Recording...", for: .normal)
let audioURL = RecordNoteViewController.getNoteSampleURL()
print(audioURL.absoluteString)
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 48000,
AVNumberOfChannelsKey: 1,
AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue
]
do {
noteSampler = try AVAudioRecorder(url: audioURL, settings: settings)
noteSampler.delegate = self
recordSampleResult = noteSampler.record(forDuration: 1)
if noteSampler.prepareToRecord() {
if recordSampleResult == false {
finishRecording(success: false)
}
}
} catch {
finishRecording(success: false)
}
recordNoteButton.isEnabled = true
}
func finishRecording(success: Bool) {
view.backgroundColor = UIColor(red: 0, green: 0.6, blue: 0, alpha: 1)
recordNoteButton.isEnabled = true
noteSampler.stop()
noteSampler = nil
if success {
recordNoteButton.setTitle("Tap to Re-record", for: .normal)
if playSampleButton.isHidden {
UIView.animate(withDuration: 0.35) { [unowned self] in
self.playSampleButton.isHidden = false
self.playSampleButton.alpha = 1
}
}
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Analyze Note", style: .plain, target: self, action: #selector(analyzeNoteTapped))
} else {
recordNoteButton.setTitle("Tap to Record", for: .normal)
let ac = UIAlertController(title: "Record failed", message: "There was a problem recording your note; please try again.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
@objc func analyzeNoteTapped() {
let anvc = AnalyzeNoteViewController()
anvc.selectedNote = selectedNote
navigationController?.pushViewController(anvc, animated: true)
}
@objc func recordTapped() {
if noteSampler == nil {
startRecording()
if !playSampleButton.isHidden {
UIView.animate(withDuration: 0.35) { [unowned self] in
self.playSampleButton.isHidden = true
self.playSampleButton.alpha = 0
}
}
}
}
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if !flag {
finishRecording(success: false)
}
}
@objc func playSampleTapped() {
let audioURL = RecordNoteViewController.getNoteSampleURL()
do {
notePlayer = try AVAudioPlayer(contentsOf: audioURL)
notePlayer.play()
} catch {
let ac = UIAlertController(title: "Playback failed", message: "There was a problem playing your note; please try re-recording.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
/
}