Building a custom audio recorder in Swift gives you full control over audio quality, file formats, and real-time power monitoring. Apple’s AVFAudio framework provides a straightforward, object-oriented way to record audio using AVAudioRecorder.
This guide covers how to set up audio permissions, configure the recording session, initialize the recorder, and track audio levels. Prerequisites To follow this tutorial, you need: Xcode 15 or later A device running iOS 17 or later (or macOS equivalent) Basic familiarity with Swift and async/await syntax Step 1: Request Privacy Permissions
iOS requires explicit user permission to access the microphone. If you skip this step, your app will crash or fail to record silently.
Open your project’s Info.plist file and add the following key with a descriptive string explaining why your app needs microphone access:
Key: Privacy - Microphone Usage Description (NSMicrophoneUsageDescription)
Value: This app requires microphone access to record custom audio clips. Step 2: Configure the AVAudioSession
The AVAudioSession acts as an intermediary between your app and the system’s audio hardware. You must set its category to allow recording before starting the recorder.
Create a dedicated service class to manage your audio logic:
import Foundation import AVFAudio @Observable final class AudioRecorderManager { private var audioRecorder: AVAudioRecorder? var isRecording = false func setupAudioSession() async throws { let session = AVAudioSession.sharedInstance() // Configure the session for record and playback try session.setCategory(.playAndRecord, mode: .default, options: [.defaultToSpeaker]) // Request microphone access asynchronously let granted = try await session.requestRecordPermission() guard granted else { throw NSError(domain: “AudioRecorder”, code: 1, userInfo: [NSLocalizedDescriptionKey: “Microphone permission denied.”]) } // Activate the session try session.setActive(true, options: .notifyOthersOnDeactivation) } } Use code with caution. Step 3: Define Recording Settings
AVAudioRecorder requires a dictionary of settings to determine the file container format, codec, sample rate, and channel count.
For high-quality, widely compatible audio, use AAC encoding inside an M4A container:
private func getRecordingSettings() -> [String: Any] { return [ AVFormatIDKey: Int(kAudioFormatMPEG4AAC), AVSampleRateKey: 44100.0, // Standard CD quality AVNumberOfChannelsKey: 2, // Stereo recording AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue ] } private func getDocumentsDirectory() -> URL { let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) return paths[0] } Use code with caution. Step 4: Implement Start and Stop Recording
With the session configured and settings defined, you can now initialize the AVAudioRecorder pointing to a local file destination. Add these functions to your AudioRecorderManager class:
func startRecording() { let audioFilename = getDocumentsDirectory().appendingPathComponent(“custom_recording.m4a”) let settings = getRecordingSettings() do { // Initialize the recorder audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings) audioRecorder?.prepareToRecord() // Start recording audioRecorder?.record() isRecording = true } catch { print(“Failed to initialize audio recorder: (error.localizedDescription)”) } } func stopRecording() { audioRecorder?.stop() audioRecorder = nil isRecording = false } Use code with caution. Step 5: Monitor Audio Levels (Optional)
If you want to build a visual wave form or a volume meter UI, you can enable metering on your recorder.
Set audioRecorder?.isMeteringEnabled = true inside your startRecording() function.
Query the decibel levels periodically using a Timer or a continuous loop:
func updateAudioMeter() -> Float { guard let recorder = audioRecorder, recorder.isRecording else { return 0.0 } recorder.updateMeters() let averagePower = recorder.averagePower(forChannel: 0) // Convert decibels (-160 to 0) to a normalized 0.0 to 1.0 range let minDb: Float = -60.0 if averagePower < minDb { return 0.0 } else if averagePower >= 0.0 { return 1.0 } else { return (averagePower - minDb) / (0.0 - minDb) } } Use code with caution. Step 6: Connect to a SwiftUI View
Now wrap your manager inside a clean SwiftUI user interface to toggle recording status. Use code with caution. Wrapping Up
You now have a functional, custom audio recorder built completely in Swift. From here, you can extend this foundation by implementing AVAudioRecorderDelegate to handle structural interruptions (like incoming phone calls) or adding an AVAudioPlayer to playback the file saved in your app’s document directory.
If you want to take this project further, let me know if you would like to:
Learn how to play back the recorded audio using AVAudioPlayer
Implement a real-time visual waveform bar chart using SwiftUI Support pausing and resuming the recording session
Leave a Reply