├── VoiceRecTest ├── Assets.xcassets │ ├── Contents.json │ ├── AppIcon.appiconset │ │ ├── Voice-rec-app-icon-128.png │ │ ├── Voice-rec-app-icon-16.png │ │ ├── Voice-rec-app-icon-256.png │ │ ├── Voice-rec-app-icon-32.png │ │ ├── Voice-rec-app-icon-512.png │ │ ├── Voice-rec-app-icon-64.png │ │ ├── Voice-rec-app-icon-1024 1.png │ │ ├── Voice-rec-app-icon-1024.png │ │ ├── Voice-rec-app-icon-256 1.png │ │ ├── Voice-rec-app-icon-32 1.png │ │ ├── Voice-rec-app-icon-512 1.png │ │ └── Contents.json │ └── AccentColor.colorset │ │ └── Contents.json ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── VoiceRecTest.xcdatamodeld │ ├── .xccurrentversion │ └── VoiceRecTest.xcdatamodel │ │ └── contents ├── VoiceRecTest.entitlements ├── App │ └── VoiceRecTestApp.swift ├── Support │ └── Extensions.swift ├── Views │ ├── ContentView.swift │ ├── RecorderBar.swift │ ├── RecordingsList.swift │ └── PlayerBar.swift ├── CoreData │ └── Persistence.swift └── AudioManager │ ├── AudioPlayer.swift │ └── AudioRecorder.swift ├── VoiceRecTest.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── umayanga.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj └── README.md /VoiceRecTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /VoiceRecTest/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-128.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-16.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-256.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-32.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-512.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-64.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-1024 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-1024 1.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-1024.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-256 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-256 1.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-32 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-32 1.png -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-512 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/USBA/VoiceRecorder/HEAD/VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Voice-rec-app-icon-512 1.png -------------------------------------------------------------------------------- /VoiceRecTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /VoiceRecTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /VoiceRecTest/VoiceRecTest.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | VoiceRecTest.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /VoiceRecTest/VoiceRecTest.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-only 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VoiceRecorder 2 | A simple voice recorder app created to try out voice recording and audio playing on SwiftUI. 3 | 4 | **Requirements** 5 | * Xcode 13 or higher 6 | * iOS 15 or higher 7 | 8 | **Technologies** 9 | * SwiftUI 10 | * AVFoundation 11 | * CoreData 12 | 13 | ![RPReplay_Final1658509976](https://user-images.githubusercontent.com/19838220/180491358-81bc82f8-5457-46d1-925c-9987e0a3c50e.gif) 14 | -------------------------------------------------------------------------------- /VoiceRecTest.xcodeproj/xcuserdata/umayanga.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | VoiceRecTest.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /VoiceRecTest/App/VoiceRecTestApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VoiceRecTestApp.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct VoiceRecTestApp: App { 12 | let persistenceController = PersistenceController.shared 13 | 14 | var body: some Scene { 15 | WindowGroup { 16 | ContentView() 17 | .environment(\.managedObjectContext, persistenceController.container.viewContext) 18 | // always use dark mode 19 | .preferredColorScheme(.dark) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /VoiceRecTest/Support/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-21. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Date { 11 | func toString( dateFormat format : String ) -> String { 12 | let dateFormatter = DateFormatter() 13 | dateFormatter.dateFormat = format 14 | return dateFormatter.string(from: self) 15 | } 16 | } 17 | 18 | extension DateComponentsFormatter { 19 | static let positional: DateComponentsFormatter = { 20 | let formatter = DateComponentsFormatter() 21 | formatter.allowedUnits = [.minute, .second] 22 | formatter.unitsStyle = .positional 23 | formatter.zeroFormattingBehavior = .pad 24 | return formatter 25 | }() 26 | } 27 | -------------------------------------------------------------------------------- /VoiceRecTest/VoiceRecTest.xcdatamodeld/VoiceRecTest.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /VoiceRecTest/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-21. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | 11 | struct ContentView: View { 12 | @Environment(\.managedObjectContext) private var moc 13 | @ObservedObject var audioPlayer = AudioPlayer() 14 | 15 | @ObservedObject var audioRecorder = AudioRecorder() 16 | 17 | @FetchRequest( 18 | sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)], 19 | animation: .default) 20 | private var items: FetchedResults 21 | 22 | 23 | var body: some View { 24 | NavigationView { 25 | RecordingsList(audioPlayer: audioPlayer) 26 | .safeAreaInset(edge: .bottom) { 27 | bottomBar 28 | } 29 | .navigationTitle("Voice Recorder") 30 | } 31 | } 32 | 33 | var bottomBar: some View { 34 | VStack { 35 | PlayerBar(audioPlayer: audioPlayer) 36 | RecorderBar(audioPlayer: audioPlayer) 37 | } 38 | .background(.thinMaterial) 39 | } 40 | 41 | } 42 | 43 | struct ContentView_Previews: PreviewProvider { 44 | static var previews: some View { 45 | ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /VoiceRecTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Voice-rec-app-icon-1024.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | }, 9 | { 10 | "filename" : "Voice-rec-app-icon-16.png", 11 | "idiom" : "mac", 12 | "scale" : "1x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "Voice-rec-app-icon-32.png", 17 | "idiom" : "mac", 18 | "scale" : "2x", 19 | "size" : "16x16" 20 | }, 21 | { 22 | "filename" : "Voice-rec-app-icon-32 1.png", 23 | "idiom" : "mac", 24 | "scale" : "1x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "Voice-rec-app-icon-64.png", 29 | "idiom" : "mac", 30 | "scale" : "2x", 31 | "size" : "32x32" 32 | }, 33 | { 34 | "filename" : "Voice-rec-app-icon-128.png", 35 | "idiom" : "mac", 36 | "scale" : "1x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "Voice-rec-app-icon-256 1.png", 41 | "idiom" : "mac", 42 | "scale" : "2x", 43 | "size" : "128x128" 44 | }, 45 | { 46 | "filename" : "Voice-rec-app-icon-256.png", 47 | "idiom" : "mac", 48 | "scale" : "1x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "Voice-rec-app-icon-512 1.png", 53 | "idiom" : "mac", 54 | "scale" : "2x", 55 | "size" : "256x256" 56 | }, 57 | { 58 | "filename" : "Voice-rec-app-icon-512.png", 59 | "idiom" : "mac", 60 | "scale" : "1x", 61 | "size" : "512x512" 62 | }, 63 | { 64 | "filename" : "Voice-rec-app-icon-1024 1.png", 65 | "idiom" : "mac", 66 | "scale" : "2x", 67 | "size" : "512x512" 68 | } 69 | ], 70 | "info" : { 71 | "author" : "xcode", 72 | "version" : 1 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /VoiceRecTest/CoreData/Persistence.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Persistence.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-21. 6 | // 7 | 8 | import CoreData 9 | 10 | struct PersistenceController { 11 | static let shared = PersistenceController() 12 | 13 | static var preview: PersistenceController = { 14 | let result = PersistenceController(inMemory: true) 15 | let viewContext = result.container.viewContext 16 | for _ in 0..<10 { 17 | let newItem = Item(context: viewContext) 18 | newItem.timestamp = Date() 19 | } 20 | do { 21 | try viewContext.save() 22 | } catch { 23 | // Replace this implementation with code to handle the error appropriately. 24 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 25 | let nsError = error as NSError 26 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)") 27 | } 28 | return result 29 | }() 30 | 31 | let container: NSPersistentContainer 32 | 33 | init(inMemory: Bool = false) { 34 | container = NSPersistentContainer(name: "VoiceRecTest") 35 | if inMemory { 36 | container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") 37 | } 38 | container.loadPersistentStores(completionHandler: { (storeDescription, error) in 39 | if let error = error as NSError? { 40 | // Replace this implementation with code to handle the error appropriately. 41 | // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 42 | 43 | /* 44 | Typical reasons for an error here include: 45 | * The parent directory does not exist, cannot be created, or disallows writing. 46 | * The persistent store is not accessible, due to permissions or data protection when the device is locked. 47 | * The device is out of space. 48 | * The store could not be migrated to the current model version. 49 | Check the error message to determine what the actual problem was. 50 | */ 51 | fatalError("Unresolved error \(error), \(error.userInfo)") 52 | } 53 | }) 54 | container.viewContext.automaticallyMergesChangesFromParent = true 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /VoiceRecTest/Views/RecorderBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecorderBar.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-22. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct RecorderBar: View { 11 | @ObservedObject var audioRecorder = AudioRecorder() 12 | @ObservedObject var audioPlayer: AudioPlayer 13 | 14 | @State var buttonSize: CGFloat = 1 15 | 16 | var repeatingAnimation: Animation { 17 | Animation.linear(duration: 0.5) 18 | .repeatForever() 19 | } 20 | 21 | var body: some View { 22 | VStack { 23 | 24 | if let audioRecorder = audioRecorder.audioRecorder, audioRecorder.isRecording { 25 | TimelineView(.periodic(from: .now, by: 1)) { _ in 26 | // recording duration 27 | Text(DateComponentsFormatter.positional.string(from: audioRecorder.currentTime) ?? "0:00") 28 | .font(.title3) 29 | .fontWeight(.semibold) 30 | } 31 | .transition(.scale) 32 | } 33 | 34 | recordButton 35 | } 36 | .padding() 37 | .frame(maxWidth: .infinity, alignment: .center) 38 | } 39 | 40 | var recordButton: some View { 41 | Button { 42 | if audioRecorder.isRecording { 43 | stopRecording() 44 | } else { 45 | startRecording() 46 | } 47 | } label: { 48 | Image(systemName: audioRecorder.isRecording ? "stop.circle.fill" : "mic.circle.fill") 49 | .resizable() 50 | .aspectRatio(contentMode: .fill) 51 | .frame(width: 65, height: 65) 52 | .clipped() 53 | .foregroundColor(.red) 54 | .scaleEffect(buttonSize) 55 | .onChange(of: audioRecorder.isRecording) { isRecording in 56 | if isRecording { 57 | withAnimation(repeatingAnimation) { buttonSize = 1.1 } 58 | } else { 59 | withAnimation { buttonSize = 1 } 60 | } 61 | } 62 | } 63 | } 64 | 65 | func startRecording() { 66 | if audioPlayer.audioPlayer?.isPlaying ?? false { 67 | // stop any playing recordings 68 | audioPlayer.stopPlayback() 69 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 70 | // Start Recording 71 | audioRecorder.startRecording() 72 | } 73 | } else { 74 | // Start Recording 75 | audioRecorder.startRecording() 76 | } 77 | } 78 | 79 | func stopRecording() { 80 | // Stop Recording 81 | audioRecorder.stopRecording() 82 | } 83 | 84 | } 85 | 86 | struct RecorderBar_Previews: PreviewProvider { 87 | static var previews: some View { 88 | RecorderBar(audioPlayer: AudioPlayer()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /VoiceRecTest/AudioManager/AudioPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioPlayer.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-21. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import Combine 11 | import AVFoundation 12 | 13 | class AudioPlayer: NSObject, ObservableObject, AVAudioPlayerDelegate { 14 | 15 | @Published var currentlyPlaying: Recording? 16 | @Published var isPlaying = false 17 | 18 | var audioPlayer: AVAudioPlayer? 19 | 20 | func startPlayback(recording: Recording) { 21 | if let recordingData = recording.recordingData { 22 | let playbackSession = AVAudioSession.sharedInstance() 23 | 24 | 25 | do { 26 | try playbackSession.setCategory(AVAudioSession.Category.playback, mode: AVAudioSession.Mode.spokenAudio) 27 | try playbackSession.setActive(true) 28 | print("Start Recording - Playback session setted") 29 | } catch { 30 | print("Play Recording - Failed to set up playback session") 31 | } 32 | 33 | do { 34 | audioPlayer = try AVAudioPlayer(data: recordingData) 35 | audioPlayer?.delegate = self 36 | audioPlayer?.play() 37 | isPlaying = true 38 | print("Play Recording - Playing") 39 | withAnimation(.spring()) { 40 | currentlyPlaying = recording 41 | } 42 | } catch { 43 | print("Play Recording - Playback failed: - \(error)") 44 | withAnimation { 45 | currentlyPlaying = nil 46 | } 47 | } 48 | } else { 49 | print("Play Recording - Could not get the recording data") 50 | withAnimation { 51 | currentlyPlaying = nil 52 | } 53 | } 54 | } 55 | 56 | func pausePlayback() { 57 | audioPlayer?.pause() 58 | isPlaying = false 59 | print("Play Recording - Paused") 60 | } 61 | 62 | func resumePlayback() { 63 | audioPlayer?.play() 64 | isPlaying = true 65 | print("Play Recording - Resumed") 66 | } 67 | 68 | func stopPlayback() { 69 | if audioPlayer != nil { 70 | audioPlayer?.stop() 71 | isPlaying = false 72 | print("Play Recording - Stopped") 73 | withAnimation(.spring()) { 74 | self.currentlyPlaying = nil 75 | } 76 | } else { 77 | print("Play Recording - Failed to Stop playing - Coz the recording is not playing") 78 | } 79 | } 80 | 81 | func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) { 82 | if flag { 83 | isPlaying = false 84 | print("Play Recording - Recoring finished playing") 85 | DispatchQueue.main.asyncAfter(deadline: .now() + 1) { 86 | withAnimation(.spring()) { 87 | self.currentlyPlaying = nil 88 | } 89 | } 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /VoiceRecTest/Views/RecordingsList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RecordingsList.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-21. 6 | // 7 | 8 | import SwiftUI 9 | import CoreData 10 | import AVFoundation 11 | 12 | struct RecordingsList: View { 13 | @Environment(\.managedObjectContext) private var moc 14 | @ObservedObject var audioPlayer: AudioPlayer 15 | 16 | @FetchRequest( 17 | sortDescriptors: [NSSortDescriptor(keyPath: \Recording.createdAt, ascending: true)], 18 | animation: .default) 19 | private var recordings: FetchedResults 20 | 21 | var body: some View { 22 | List { 23 | ForEach(recordings, id: \.id) { recording in 24 | RecordingRow(audioPlayer: audioPlayer, recording: recording) 25 | } 26 | .onDelete(perform: delete) 27 | } 28 | } 29 | 30 | func delete(at offsets: IndexSet) { 31 | withAnimation { 32 | offsets.map { recordings[$0] }.forEach(moc.delete) 33 | 34 | do { 35 | try moc.save() 36 | } catch { 37 | let nsError = error as NSError 38 | fatalError("Unresolved error \(nsError), \(nsError.userInfo)") 39 | } 40 | } 41 | } 42 | } 43 | 44 | struct RecordingRow: View { 45 | @ObservedObject var audioPlayer: AudioPlayer 46 | var recording: Recording 47 | 48 | var isPlayingThisRecording: Bool { 49 | audioPlayer.currentlyPlaying?.id == recording.id 50 | } 51 | 52 | var body: some View { 53 | HStack { 54 | VStack(alignment: .leading) { 55 | Text(recording.name ?? "Recording") 56 | .fontWeight(isPlayingThisRecording ? .bold : .regular) 57 | Group { 58 | if let recordingData = recording.recordingData, let duration = getDuration(of: recordingData) { 59 | Text(DateComponentsFormatter.positional.string(from: duration) ?? "0:00") 60 | .font(.caption2) 61 | .foregroundColor(.secondary) 62 | } 63 | } 64 | } 65 | Spacer() 66 | Button { 67 | audioPlayer.startPlayback(recording: recording) 68 | } label: { 69 | Image(systemName: "play.circle.fill") 70 | .imageScale(.large) 71 | .symbolRenderingMode(.palette) 72 | .foregroundStyle(.primary, .tertiary) 73 | } 74 | } 75 | .tint(isPlayingThisRecording ? .green : .blue) 76 | } 77 | 78 | func getDuration(of recordingData: Data) -> TimeInterval? { 79 | do { 80 | return try AVAudioPlayer(data: recordingData).duration 81 | } catch { 82 | print("Failed to get the duration for recording on the list: Recording Name - \(recording.name ?? "")") 83 | return nil 84 | } 85 | } 86 | } 87 | 88 | struct RecordingsList_Previews: PreviewProvider { 89 | static var previews: some View { 90 | RecordingsList(audioPlayer: AudioPlayer()) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /VoiceRecTest/Views/PlayerBar.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlayerBar.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct PlayerBar: View { 11 | @ObservedObject var audioPlayer: AudioPlayer 12 | @State var sliderValue: Double = 0.0 13 | @State private var isDragging = false 14 | 15 | let timer = Timer 16 | .publish(every: 0.5, on: .main, in: .common) 17 | .autoconnect() 18 | 19 | var body: some View { 20 | if let player = audioPlayer.audioPlayer, let currentlyPlaying = audioPlayer.currentlyPlaying { 21 | VStack { 22 | 23 | // Slider 24 | Slider(value: $sliderValue, in: 0...player.duration) { dragging in 25 | print("Editing the slider: \(dragging)") 26 | isDragging = dragging 27 | if !dragging { 28 | player.currentTime = sliderValue 29 | } 30 | } 31 | .tint(.primary) 32 | 33 | // Time passed & Time remaining 34 | HStack { 35 | Text(DateComponentsFormatter.positional.string(from: player.currentTime) ?? "0:00") 36 | Spacer() 37 | Text("-\(DateComponentsFormatter.positional.string(from: (player.duration - player.currentTime) ) ?? "0:00")") 38 | } 39 | .font(.caption) 40 | .foregroundColor(.secondary) 41 | 42 | HStack(spacing: 15) { 43 | // Play/Pause Button 44 | Button { 45 | if audioPlayer.isPlaying { 46 | // Pause 47 | audioPlayer.pausePlayback() 48 | } else { 49 | // Play 50 | audioPlayer.resumePlayback() 51 | } 52 | } label: { 53 | Image(systemName: audioPlayer.isPlaying ? "pause.fill" : "play.fill") 54 | .font(.title2) 55 | .imageScale(.large) 56 | } 57 | 58 | // Recording name 59 | Text(currentlyPlaying.name ?? "Recording") 60 | .fontWeight(.semibold) 61 | .lineLimit(1) 62 | 63 | Spacer() 64 | 65 | // Stop button 66 | Button { 67 | audioPlayer.stopPlayback() 68 | } label: { 69 | Image(systemName: "xmark.circle.fill") 70 | .font(.title2) 71 | .imageScale(.large) 72 | .symbolRenderingMode(.hierarchical) 73 | } 74 | 75 | } 76 | .padding(.top, 10) 77 | } 78 | .padding() 79 | .foregroundColor(.primary) 80 | .onAppear { 81 | sliderValue = 0 82 | } 83 | .onReceive(timer) { _ in 84 | guard let player = audioPlayer.audioPlayer, !isDragging else { return } 85 | sliderValue = player.currentTime 86 | } 87 | .transition(.scale(scale: 0, anchor: .bottom)) 88 | 89 | Divider() 90 | } 91 | } 92 | } 93 | 94 | struct PlayerBar_Previews: PreviewProvider { 95 | static var previews: some View { 96 | PlayerBar(audioPlayer: AudioPlayer()) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /VoiceRecTest/AudioManager/AudioRecorder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AudioRecorder.swift 3 | // VoiceRecTest 4 | // 5 | // Created by Umayanga Alahakoon on 2022-07-21. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | import AVFoundation 11 | import Combine 12 | import CoreData 13 | 14 | class AudioRecorder: NSObject,ObservableObject { 15 | 16 | let moc = PersistenceController.shared.container.viewContext 17 | 18 | var audioRecorder: AVAudioRecorder? 19 | 20 | @Published private var recordingName = "Recording1" 21 | @Published private var recordingDate = Date() 22 | @Published private var recordingURL: URL? 23 | 24 | @Published var isRecording = false 25 | 26 | // MARK: - Start Recording 27 | 28 | func startRecording() { 29 | let recordingSession = AVAudioSession.sharedInstance() 30 | 31 | do { 32 | try recordingSession.setCategory(.playAndRecord, mode: .default) 33 | try recordingSession.setActive(true) 34 | print("Start Recording - Recording session setted") 35 | } catch { 36 | print("Start Recording - Failed to set up recording session") 37 | } 38 | 39 | let currentDateTime = Date.now 40 | 41 | recordingDate = currentDateTime 42 | recordingName = "\(currentDateTime.toString(dateFormat: "dd-MM-YY_'at'_HH:mm:ss"))" 43 | 44 | // save the recording to the temporary directory 45 | let tempDirectory = FileManager.default.temporaryDirectory 46 | let recordingFileURL = tempDirectory.appendingPathComponent(recordingName).appendingPathExtension("m4a") 47 | recordingURL = recordingFileURL 48 | 49 | let settings = [ 50 | AVFormatIDKey: Int(kAudioFormatMPEG4AAC), 51 | AVSampleRateKey: 12000, 52 | AVNumberOfChannelsKey: 1, 53 | AVEncoderAudioQualityKey: AVAudioQuality.high.rawValue 54 | ] 55 | 56 | do { 57 | audioRecorder = try AVAudioRecorder(url: recordingFileURL, settings: settings) 58 | audioRecorder?.record() 59 | 60 | withAnimation { 61 | isRecording = true 62 | } 63 | print("Start Recording - Recording Started") 64 | } catch { 65 | print("Start Recording - Could not start recording") 66 | } 67 | } 68 | 69 | // MARK: - Stop Recording 70 | 71 | func stopRecording() { 72 | audioRecorder?.stop() 73 | withAnimation { 74 | isRecording = false 75 | } 76 | 77 | if let recordingURL { 78 | do { 79 | let recordingDate = try Data(contentsOf: recordingURL) 80 | print("Stop Recording - Saving to CoreData") 81 | // save the recording to CoreData 82 | saveRecordingOnCoreData(recordingData: recordingDate) 83 | } catch { 84 | print("Stop Recording - Could not save to CoreData - Cannot get the recording data from URL: \(error)") 85 | } 86 | 87 | } else { 88 | print("Stop Recording - Could not save to CoreData - Cannot find the recording URL") 89 | } 90 | 91 | } 92 | 93 | // MARK: - CoreData -------------------------------------- 94 | 95 | func saveRecordingOnCoreData(recordingData: Data) { 96 | let newRecording = Recording(context: moc) 97 | newRecording.id = UUID() 98 | newRecording.name = recordingName 99 | newRecording.createdAt = recordingDate 100 | newRecording.recordingData = recordingData 101 | 102 | do { 103 | try moc.save() 104 | print("Stop Recording - Successfully saved to CoreData") 105 | // delete the recording stored in the temporary directory 106 | deleteRecordingFile() 107 | } catch { 108 | let nsError = error as NSError 109 | fatalError("Stop Recording - Failed to save to CoreData - Unresolved error \(nsError), \(nsError.userInfo)") 110 | } 111 | } 112 | 113 | func deleteRecordingFile() { 114 | if let recordingURL { 115 | do { 116 | try FileManager.default.removeItem(at: recordingURL) 117 | print("Stop Recording - Successfully deleted the recording file") 118 | } catch { 119 | print("Stop Recording - Could not delete the recording file - Cannot find the recording URL") 120 | } 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /VoiceRecTest.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0236343E288A96D3008A0944 /* RecorderBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0236343D288A96D3008A0944 /* RecorderBar.swift */; }; 11 | 027811B92889753F00A4A61D /* VoiceRecTestApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027811B82889753F00A4A61D /* VoiceRecTestApp.swift */; }; 12 | 027811BB2889753F00A4A61D /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027811BA2889753F00A4A61D /* Persistence.swift */; }; 13 | 027811BE2889753F00A4A61D /* VoiceRecTest.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 027811BC2889753F00A4A61D /* VoiceRecTest.xcdatamodeld */; }; 14 | 027811C02889753F00A4A61D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027811BF2889753F00A4A61D /* ContentView.swift */; }; 15 | 027811C22889754300A4A61D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 027811C12889754300A4A61D /* Assets.xcassets */; }; 16 | 027811C62889754300A4A61D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 027811C52889754300A4A61D /* Preview Assets.xcassets */; }; 17 | 027811D428897A5600A4A61D /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027811D328897A5600A4A61D /* Extensions.swift */; }; 18 | 027811D728897A8600A4A61D /* AudioRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027811D628897A8600A4A61D /* AudioRecorder.swift */; }; 19 | 027811D928897A9900A4A61D /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027811D828897A9900A4A61D /* AudioPlayer.swift */; }; 20 | 027811DB28897AD200A4A61D /* RecordingsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027811DA28897AD200A4A61D /* RecordingsList.swift */; }; 21 | 027811DD2889932100A4A61D /* PlayerBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 027811DC2889932100A4A61D /* PlayerBar.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 0236343D288A96D3008A0944 /* RecorderBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecorderBar.swift; sourceTree = ""; }; 26 | 027811B52889753F00A4A61D /* VoiceRecTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = VoiceRecTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | 027811B82889753F00A4A61D /* VoiceRecTestApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VoiceRecTestApp.swift; sourceTree = ""; }; 28 | 027811BA2889753F00A4A61D /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; 29 | 027811BD2889753F00A4A61D /* VoiceRecTest.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = VoiceRecTest.xcdatamodel; sourceTree = ""; }; 30 | 027811BF2889753F00A4A61D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 31 | 027811C12889754300A4A61D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 32 | 027811C32889754300A4A61D /* VoiceRecTest.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = VoiceRecTest.entitlements; sourceTree = ""; }; 33 | 027811C52889754300A4A61D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 34 | 027811D328897A5600A4A61D /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; }; 35 | 027811D628897A8600A4A61D /* AudioRecorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioRecorder.swift; sourceTree = ""; }; 36 | 027811D828897A9900A4A61D /* AudioPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = ""; }; 37 | 027811DA28897AD200A4A61D /* RecordingsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecordingsList.swift; sourceTree = ""; }; 38 | 027811DC2889932100A4A61D /* PlayerBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerBar.swift; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 027811B22889753F00A4A61D /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 021DAD69288AC939005AE5D1 /* App */ = { 53 | isa = PBXGroup; 54 | children = ( 55 | 027811B82889753F00A4A61D /* VoiceRecTestApp.swift */, 56 | ); 57 | path = App; 58 | sourceTree = ""; 59 | }; 60 | 027811AC2889753F00A4A61D = { 61 | isa = PBXGroup; 62 | children = ( 63 | 027811B72889753F00A4A61D /* VoiceRecTest */, 64 | 027811B62889753F00A4A61D /* Products */, 65 | ); 66 | sourceTree = ""; 67 | }; 68 | 027811B62889753F00A4A61D /* Products */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 027811B52889753F00A4A61D /* VoiceRecTest.app */, 72 | ); 73 | name = Products; 74 | sourceTree = ""; 75 | }; 76 | 027811B72889753F00A4A61D /* VoiceRecTest */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 021DAD69288AC939005AE5D1 /* App */, 80 | 027811D528897A6800A4A61D /* AudioManager */, 81 | 027811CC2889786700A4A61D /* Views */, 82 | 027811D028897A2E00A4A61D /* Support */, 83 | 027811DE28899C9100A4A61D /* CoreData */, 84 | 027811C12889754300A4A61D /* Assets.xcassets */, 85 | 027811C32889754300A4A61D /* VoiceRecTest.entitlements */, 86 | 027811BC2889753F00A4A61D /* VoiceRecTest.xcdatamodeld */, 87 | 027811C42889754300A4A61D /* Preview Content */, 88 | ); 89 | path = VoiceRecTest; 90 | sourceTree = ""; 91 | }; 92 | 027811C42889754300A4A61D /* Preview Content */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 027811C52889754300A4A61D /* Preview Assets.xcassets */, 96 | ); 97 | path = "Preview Content"; 98 | sourceTree = ""; 99 | }; 100 | 027811CC2889786700A4A61D /* Views */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 027811BF2889753F00A4A61D /* ContentView.swift */, 104 | 027811DA28897AD200A4A61D /* RecordingsList.swift */, 105 | 027811DC2889932100A4A61D /* PlayerBar.swift */, 106 | 0236343D288A96D3008A0944 /* RecorderBar.swift */, 107 | ); 108 | path = Views; 109 | sourceTree = ""; 110 | }; 111 | 027811D028897A2E00A4A61D /* Support */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 027811D328897A5600A4A61D /* Extensions.swift */, 115 | ); 116 | path = Support; 117 | sourceTree = ""; 118 | }; 119 | 027811D528897A6800A4A61D /* AudioManager */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 027811D628897A8600A4A61D /* AudioRecorder.swift */, 123 | 027811D828897A9900A4A61D /* AudioPlayer.swift */, 124 | ); 125 | path = AudioManager; 126 | sourceTree = ""; 127 | }; 128 | 027811DE28899C9100A4A61D /* CoreData */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 027811BA2889753F00A4A61D /* Persistence.swift */, 132 | ); 133 | path = CoreData; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 027811B42889753F00A4A61D /* VoiceRecTest */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 027811C92889754300A4A61D /* Build configuration list for PBXNativeTarget "VoiceRecTest" */; 142 | buildPhases = ( 143 | 027811B12889753F00A4A61D /* Sources */, 144 | 027811B22889753F00A4A61D /* Frameworks */, 145 | 027811B32889753F00A4A61D /* Resources */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | ); 151 | name = VoiceRecTest; 152 | productName = VoiceRecTest; 153 | productReference = 027811B52889753F00A4A61D /* VoiceRecTest.app */; 154 | productType = "com.apple.product-type.application"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | 027811AD2889753F00A4A61D /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | BuildIndependentTargetsInParallel = 1; 163 | LastSwiftUpdateCheck = 1400; 164 | LastUpgradeCheck = 1400; 165 | TargetAttributes = { 166 | 027811B42889753F00A4A61D = { 167 | CreatedOnToolsVersion = 14.0; 168 | }; 169 | }; 170 | }; 171 | buildConfigurationList = 027811B02889753F00A4A61D /* Build configuration list for PBXProject "VoiceRecTest" */; 172 | compatibilityVersion = "Xcode 14.0"; 173 | developmentRegion = en; 174 | hasScannedForEncodings = 0; 175 | knownRegions = ( 176 | en, 177 | Base, 178 | ); 179 | mainGroup = 027811AC2889753F00A4A61D; 180 | productRefGroup = 027811B62889753F00A4A61D /* Products */; 181 | projectDirPath = ""; 182 | projectRoot = ""; 183 | targets = ( 184 | 027811B42889753F00A4A61D /* VoiceRecTest */, 185 | ); 186 | }; 187 | /* End PBXProject section */ 188 | 189 | /* Begin PBXResourcesBuildPhase section */ 190 | 027811B32889753F00A4A61D /* Resources */ = { 191 | isa = PBXResourcesBuildPhase; 192 | buildActionMask = 2147483647; 193 | files = ( 194 | 027811C62889754300A4A61D /* Preview Assets.xcassets in Resources */, 195 | 027811C22889754300A4A61D /* Assets.xcassets in Resources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXResourcesBuildPhase section */ 200 | 201 | /* Begin PBXSourcesBuildPhase section */ 202 | 027811B12889753F00A4A61D /* Sources */ = { 203 | isa = PBXSourcesBuildPhase; 204 | buildActionMask = 2147483647; 205 | files = ( 206 | 027811B92889753F00A4A61D /* VoiceRecTestApp.swift in Sources */, 207 | 027811BB2889753F00A4A61D /* Persistence.swift in Sources */, 208 | 027811D928897A9900A4A61D /* AudioPlayer.swift in Sources */, 209 | 0236343E288A96D3008A0944 /* RecorderBar.swift in Sources */, 210 | 027811C02889753F00A4A61D /* ContentView.swift in Sources */, 211 | 027811BE2889753F00A4A61D /* VoiceRecTest.xcdatamodeld in Sources */, 212 | 027811DD2889932100A4A61D /* PlayerBar.swift in Sources */, 213 | 027811DB28897AD200A4A61D /* RecordingsList.swift in Sources */, 214 | 027811D728897A8600A4A61D /* AudioRecorder.swift in Sources */, 215 | 027811D428897A5600A4A61D /* Extensions.swift in Sources */, 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXSourcesBuildPhase section */ 220 | 221 | /* Begin XCBuildConfiguration section */ 222 | 027811C72889754300A4A61D /* Debug */ = { 223 | isa = XCBuildConfiguration; 224 | buildSettings = { 225 | ALWAYS_SEARCH_USER_PATHS = NO; 226 | CLANG_ANALYZER_NONNULL = YES; 227 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 228 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 229 | CLANG_ENABLE_MODULES = YES; 230 | CLANG_ENABLE_OBJC_ARC = YES; 231 | CLANG_ENABLE_OBJC_WEAK = YES; 232 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 233 | CLANG_WARN_BOOL_CONVERSION = YES; 234 | CLANG_WARN_COMMA = YES; 235 | CLANG_WARN_CONSTANT_CONVERSION = YES; 236 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 237 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 238 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 239 | CLANG_WARN_EMPTY_BODY = YES; 240 | CLANG_WARN_ENUM_CONVERSION = YES; 241 | CLANG_WARN_INFINITE_RECURSION = YES; 242 | CLANG_WARN_INT_CONVERSION = YES; 243 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 244 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 245 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 247 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 248 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 249 | CLANG_WARN_STRICT_PROTOTYPES = YES; 250 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 251 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 252 | CLANG_WARN_UNREACHABLE_CODE = YES; 253 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 254 | COPY_PHASE_STRIP = NO; 255 | DEBUG_INFORMATION_FORMAT = dwarf; 256 | ENABLE_STRICT_OBJC_MSGSEND = YES; 257 | ENABLE_TESTABILITY = YES; 258 | GCC_C_LANGUAGE_STANDARD = gnu11; 259 | GCC_DYNAMIC_NO_PIC = NO; 260 | GCC_NO_COMMON_BLOCKS = YES; 261 | GCC_OPTIMIZATION_LEVEL = 0; 262 | GCC_PREPROCESSOR_DEFINITIONS = ( 263 | "DEBUG=1", 264 | "$(inherited)", 265 | ); 266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 268 | GCC_WARN_UNDECLARED_SELECTOR = YES; 269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 270 | GCC_WARN_UNUSED_FUNCTION = YES; 271 | GCC_WARN_UNUSED_VARIABLE = YES; 272 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 273 | MTL_FAST_MATH = YES; 274 | ONLY_ACTIVE_ARCH = YES; 275 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 276 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 277 | }; 278 | name = Debug; 279 | }; 280 | 027811C82889754300A4A61D /* Release */ = { 281 | isa = XCBuildConfiguration; 282 | buildSettings = { 283 | ALWAYS_SEARCH_USER_PATHS = NO; 284 | CLANG_ANALYZER_NONNULL = YES; 285 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 286 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 287 | CLANG_ENABLE_MODULES = YES; 288 | CLANG_ENABLE_OBJC_ARC = YES; 289 | CLANG_ENABLE_OBJC_WEAK = YES; 290 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 291 | CLANG_WARN_BOOL_CONVERSION = YES; 292 | CLANG_WARN_COMMA = YES; 293 | CLANG_WARN_CONSTANT_CONVERSION = YES; 294 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 295 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 296 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 297 | CLANG_WARN_EMPTY_BODY = YES; 298 | CLANG_WARN_ENUM_CONVERSION = YES; 299 | CLANG_WARN_INFINITE_RECURSION = YES; 300 | CLANG_WARN_INT_CONVERSION = YES; 301 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 302 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 303 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 304 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 305 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 306 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 307 | CLANG_WARN_STRICT_PROTOTYPES = YES; 308 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 309 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 310 | CLANG_WARN_UNREACHABLE_CODE = YES; 311 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 312 | COPY_PHASE_STRIP = NO; 313 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 314 | ENABLE_NS_ASSERTIONS = NO; 315 | ENABLE_STRICT_OBJC_MSGSEND = YES; 316 | GCC_C_LANGUAGE_STANDARD = gnu11; 317 | GCC_NO_COMMON_BLOCKS = YES; 318 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 319 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 320 | GCC_WARN_UNDECLARED_SELECTOR = YES; 321 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 322 | GCC_WARN_UNUSED_FUNCTION = YES; 323 | GCC_WARN_UNUSED_VARIABLE = YES; 324 | MTL_ENABLE_DEBUG_INFO = NO; 325 | MTL_FAST_MATH = YES; 326 | SWIFT_COMPILATION_MODE = wholemodule; 327 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 328 | }; 329 | name = Release; 330 | }; 331 | 027811CA2889754300A4A61D /* Debug */ = { 332 | isa = XCBuildConfiguration; 333 | buildSettings = { 334 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 335 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 336 | CODE_SIGN_ENTITLEMENTS = VoiceRecTest/VoiceRecTest.entitlements; 337 | CODE_SIGN_STYLE = Automatic; 338 | CURRENT_PROJECT_VERSION = 1; 339 | DEVELOPMENT_ASSET_PATHS = "\"VoiceRecTest/Preview Content\""; 340 | DEVELOPMENT_TEAM = BJY4B596D6; 341 | ENABLE_HARDENED_RUNTIME = YES; 342 | ENABLE_PREVIEWS = YES; 343 | GENERATE_INFOPLIST_FILE = YES; 344 | INFOPLIST_KEY_CFBundleDisplayName = "Voice Recorder"; 345 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 346 | INFOPLIST_KEY_NSMicrophoneUsageDescription = "Need access to the microphone in order to record audio"; 347 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 348 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 349 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 350 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 351 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 352 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 353 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 354 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 355 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 356 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 357 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 358 | MACOSX_DEPLOYMENT_TARGET = 12.0; 359 | MARKETING_VERSION = 1.0; 360 | PRODUCT_BUNDLE_IDENTIFIER = com.umayanga.VoiceRecorder; 361 | PRODUCT_NAME = "$(TARGET_NAME)"; 362 | SDKROOT = auto; 363 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 364 | SWIFT_EMIT_LOC_STRINGS = YES; 365 | SWIFT_VERSION = 5.0; 366 | TARGETED_DEVICE_FAMILY = "1,2"; 367 | }; 368 | name = Debug; 369 | }; 370 | 027811CB2889754300A4A61D /* Release */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 374 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 375 | CODE_SIGN_ENTITLEMENTS = VoiceRecTest/VoiceRecTest.entitlements; 376 | CODE_SIGN_STYLE = Automatic; 377 | CURRENT_PROJECT_VERSION = 1; 378 | DEVELOPMENT_ASSET_PATHS = "\"VoiceRecTest/Preview Content\""; 379 | DEVELOPMENT_TEAM = BJY4B596D6; 380 | ENABLE_HARDENED_RUNTIME = YES; 381 | ENABLE_PREVIEWS = YES; 382 | GENERATE_INFOPLIST_FILE = YES; 383 | INFOPLIST_KEY_CFBundleDisplayName = "Voice Recorder"; 384 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; 385 | INFOPLIST_KEY_NSMicrophoneUsageDescription = "Need access to the microphone in order to record audio"; 386 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; 387 | "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; 388 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphoneos*]" = YES; 389 | "INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents[sdk=iphonesimulator*]" = YES; 390 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphoneos*]" = YES; 391 | "INFOPLIST_KEY_UILaunchScreen_Generation[sdk=iphonesimulator*]" = YES; 392 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 393 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 394 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 395 | LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; 396 | "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; 397 | MACOSX_DEPLOYMENT_TARGET = 12.0; 398 | MARKETING_VERSION = 1.0; 399 | PRODUCT_BUNDLE_IDENTIFIER = com.umayanga.VoiceRecorder; 400 | PRODUCT_NAME = "$(TARGET_NAME)"; 401 | SDKROOT = auto; 402 | SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; 403 | SWIFT_EMIT_LOC_STRINGS = YES; 404 | SWIFT_VERSION = 5.0; 405 | TARGETED_DEVICE_FAMILY = "1,2"; 406 | }; 407 | name = Release; 408 | }; 409 | /* End XCBuildConfiguration section */ 410 | 411 | /* Begin XCConfigurationList section */ 412 | 027811B02889753F00A4A61D /* Build configuration list for PBXProject "VoiceRecTest" */ = { 413 | isa = XCConfigurationList; 414 | buildConfigurations = ( 415 | 027811C72889754300A4A61D /* Debug */, 416 | 027811C82889754300A4A61D /* Release */, 417 | ); 418 | defaultConfigurationIsVisible = 0; 419 | defaultConfigurationName = Release; 420 | }; 421 | 027811C92889754300A4A61D /* Build configuration list for PBXNativeTarget "VoiceRecTest" */ = { 422 | isa = XCConfigurationList; 423 | buildConfigurations = ( 424 | 027811CA2889754300A4A61D /* Debug */, 425 | 027811CB2889754300A4A61D /* Release */, 426 | ); 427 | defaultConfigurationIsVisible = 0; 428 | defaultConfigurationName = Release; 429 | }; 430 | /* End XCConfigurationList section */ 431 | 432 | /* Begin XCVersionGroup section */ 433 | 027811BC2889753F00A4A61D /* VoiceRecTest.xcdatamodeld */ = { 434 | isa = XCVersionGroup; 435 | children = ( 436 | 027811BD2889753F00A4A61D /* VoiceRecTest.xcdatamodel */, 437 | ); 438 | currentVersion = 027811BD2889753F00A4A61D /* VoiceRecTest.xcdatamodel */; 439 | path = VoiceRecTest.xcdatamodeld; 440 | sourceTree = ""; 441 | versionGroupType = wrapper.xcdatamodel; 442 | }; 443 | /* End XCVersionGroup section */ 444 | }; 445 | rootObject = 027811AD2889753F00A4A61D /* Project object */; 446 | } 447 | --------------------------------------------------------------------------------