Xcode swift - AVAudio Player not working after Copy Bundle - swift

I am working on using AVAudio player and I am currently getting the following error - Fatal error: Unexpectedly found nil while unwrapping an Optional value: file
I looked and on various solutions the problem was that the desired file was not in the copy bundle resources area. But the file has been added there to the desired target, so not sure of the solution.
import Foundation
import Capacitor
#objc(Buckfast)
public class Buckfast: CAPPlugin {
#objc func echo(_ call: CAPPluginCall) {
let value = call.getString("value") ?? ""
call.success([
"value": value
])
var bombSoundEffect: AVAudioPlayer?
if let path = Bundle.main.path(forResource: "1", ofType: "wav") {
let url = URL(fileURLWithPath: path)
do {
bombSoundEffect = try AVAudioPlayer(contentsOf: url)
bombSoundEffect?.play()
} catch {
// couldn't load file :(
}
}
}
}
Copy Bundle Image
Code Screenshot

You can try to unwrap using if let like
if let path = Bundle.main.path(forResource: "1", ofType: "wav") {
}
The file was not found when residing in the pods folder. The file needs to be placed in the Apps Copy Bundle Resources instead.

Related

How do you fix the "found nil while unwrapping optional value" error when trying to play sound?

I'm making a function to play sound
func playSound(soundName: String) {
let url = Bundle.main.url(forResource: soundName, withExtension: "wav")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
Then call this function in an IBAction that contains all my buttons
#IBAction func buttonPiano(_ sender: UIButton) {
playSound(soundName: String(sender.currentTitle!))
sender.backgroundColor = UIColor.white
sender.alpha = 0.3
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
sender.backgroundColor = UIColor.systemBackground
sender.alpha = 1
})
}
Running the app I can do. But whenever you press a button, it crashes and gives me this error:
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file /Users/administrator/Desktop/Xcode Projects/pianoButtons/pianoButtons/ViewController.swift, line 37
The optional value seems to be url! from my sound function.
I've tried all I could, but no luck. How do I avoid this error and play the sound without crashes?
Make sure that your soundName.wave file is shown inside the copy bundle resources. You can find that by clicking on your project > selecting your target > Build Phases > Copy Bundle Resources. If you do not see it there, click the plus button to add it.
var soundPlayer: AVAudioPlayer?
func playSentSound() {
DispatchQueue.main.async{
let path = Bundle.main.path(forResource: "soundName.mp3", ofType: nil)!
let url = URL(fileURLWithPath: path)
do {
self.soundPlayer = try AVAudioPlayer(contentsOf: url)
print("Playing")
self.soundPlayer?.play()
} catch {
// couldn't load file :(
print("Cant Load File")
}
}
}
Your code is essentially calling this
try! AVAudioPlayer(contentsOf: Bundle.main.url(forResource: String(button.currentTitle!), withExtension: "wav")!)
Each ! is a potential crash. This is when they would occur
The button may not have a current title at the time the action is triggered.
The main bundle may not have a resource with that name/extension
The audio player may not be able to play the contents of that file
In your specific case it seems like it is failing at 2. The nicer way to handle this is like so
if let url = Bundle.main.url(forResource: soundName, withExtension: "wav") {
player = try! AVAudioPlayer(contentsOf: url)
} else {
print("No resouce named \(soundName).wav")
}
First of all your app will just not play a sound instead of crashing, second you will get a helpful log message which might show you why the resource isn't being found.
Ideally all of your ! should be replaced with similar constructs, to log errors or perform some fallback action instead of crashing.

Swift AVAudioPlayer (Xcode) - Play Audio File Outside Application Bundle

In my Xcode project, I have the following code set up (simplified code):
import Cocoa
import AVKit
class ViewController: NSViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
do {
guard let filePath = Bundle.main.path(forResource: "sample", ofType: "mp3") else {
print("ERROR: Failed to retrieve music file Path")
return
}
let fileURL = URL.init(fileURLWithPath: filePath)
print(" PATH: \(filePath)")
print(" URL: \(fileURL)")
try audioPlayer = AVAudioPlayer.init(contentsOf: fileURL)
} catch {
print("ERROR: Failed to retrieve music file URL")
}
audioPlayer.play()
}
}
//CONSOLE PRINTS ----------:
// PATH: /Users/vakho/Library/Developer/Xcode/DerivedData/MusicPlayer-foqzsckozmcjnofvlvhuwabfssqi/Build/Products/Debug/MusicPlayer.app/Contents/Resources/sample.mp3
// URL: file:///Users/vakho/Library/Developer/Xcode/DerivedData/MusicPlayer-foqzsckozmcjnofvlvhuwabfssqi/Build/Products/Debug/MusicPlayer.app/Contents/Resources/sample.mp3
I am successfully able to pass the filePath of sample.mp3 (contained in application bundle) to audioPlayer by converting to URL first. Calling play() function plays the audio file.
However, since I am creating a music player app, I would like to be able to play an audio file that resides in directory folders, such as desktop, downloads folder, etc.... But when I attempt to pass a filePath of audio file outside app bundle, the code breaks:
import Cocoa
import AVKit
class ViewController: NSViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
do {
let filePathOptional: String? = "/Users/vakho/Downloads/sample.mp3"
guard let filePath = filePathOptional else {
print("ERROR: Failed to retrieve music file Path")
return
}
let fileURL = URL.init(fileURLWithPath: filePath)
print(" PATH: \(filePath)")
print(" URL: \(fileURL)")
try audioPlayer = AVAudioPlayer.init(contentsOf: fileURL)
} catch {
print("ERROR: Failed to retrieve music file URL")
}
audioPlayer.play()
}
}
//CONSOLE PRINTS ----------:
// PATH: /Users/vakho/Downloads/sample.mp3
// URL: file:///Users/vakho/Downloads/sample.mp3
//ERROR: Failed to retrieve music file URL
//ERRORS ----------:
// (lldb)
// Thread 1: EXC_BAD_ACCESS (code=1, address=0x38)
From what I could conclude, AVAudioPlayer and AVKit library is designed for accessing app assets and bundle media files, as well as streaming media from the internet. But it fails to play media from directory.
I have found several threads about the issue, all incomplete or unanswered, such as: https://forums.developer.apple.com/thread/18272
So if AVKit cannot do what I thought it should have, what library or approach can I use to access directory files? Music player apps for OSX are of course able to prompt user to scan directory and access music files. How are they doing it?
Seems that problem is here:
guard let filePath = filePathOptional else {
print("ERROR: Failed to retrieve music file Path")
return
}
Please change the print to:
print("ERROR: Failed to retrieve music file Path \(error.localizedDescription)")
I think you'll see that the problem is that you have no access to this file.
By default you have access only to sandbox of your app and Desktop folder.
Also try to put your file to Desktop and play it.

Read file with swift in iphone

I need to read a file with swift in my iphone.
In my computer I use this code and function correctly. The file "test.txt" is in my Desktop.
import UIKit
class Controller: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let sourcePath = URL(fileURLWithPath: "/Users/myname/Desktop", isDirectory: true)
let file : URL = URL(fileURLWithPath: "test.txt", relativeTo: sourcePath)
let filemgr = FileManager.default
if filemgr.fileExists(atPath: file.path){
do{
//Code to Parse text
} catch let error as NSError{
print ("Error: \(error)")
}
}
}
}
The problem is that I need to read this file in my iphone. But I don't know which is the URL to read the file. Where I can save the file and which is the URL?
Thanks
Simple answer is for this question is you can not read file from you desktop to your device. That file should be in your app document directory or you need to add that file into your project.
After that you can read it into device.
1- Drag the text file to the project and select copy Items If needed
2- Use this to read it
do {
if let path = Bundle.main.path(forResource: "fileName", ofType: "txt") {
let str = try String(contentsOfFile:path, encoding: .utf8)
print(str)
}
}
catch {
print(error)
}

fatal error: unexpectedly found nil while unwrapping an Optional value when using AudioPlayer in Swift 2

Hi I am trying to play a music file with a following code in swift 2. Basically I just dragged the audio file with a name f.mp3 to the asses folder and it my code breaks with the following message:
unexpectedly found nil while unwrapping an Optional value. Where exactly I need to put my mp3 file so the IOS can find it. Thank you
var audioPlayer: AVAudioPlayer! = nil
func playMyFile() {
let path = NSBundle.mainBundle().pathForResource("f", ofType: "mp3")
let fileURL = NSURL(fileURLWithPath: path)
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: fileURL)
} catch {
print("error")
}
audioPlayer.prepareToPlay()
audioPlayer.delegate = self
audioPlayer.play()
}
Your code is working fine with my project and here is my complete code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer! = nil
override func viewDidLoad() {
super.viewDidLoad()
playMyFile()
}
func playMyFile() {
let path = NSBundle.mainBundle().pathForResource("f", ofType: "mp3")
let fileURL = NSURL(fileURLWithPath: path!)
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: fileURL)
} catch {
print("error")
}
audioPlayer.prepareToPlay()
audioPlayer.play()
}
}
Make sure your audio is added into Copy Bundle Resources like this:
If not added then add it this way:
Check THIS sample for more Info.

How can I read a file in a swift playground

Im trying to read a text file using a Swift playground with the following
let dirs : String[]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as? String[]
if (dirs != nil) {
let directories:String[] = dirs!;
let dir = directories[0]; //documents directory
let path = dir.stringByAppendingPathComponent(file);
//read
let content = String.stringWithContentsOfFile(path, encoding: NSUTF8StringEncoding, error: nil)
}
However this fails with no error. It seems the first line stops the playground from outputting anything below
You can also put your file into your playground's resources. To do this: show Project Navigator with CMD + 1. Drag and drop your file into the resources folder. Then read the file:
On Xcode 6.4 and Swift 1.2:
var error: NSError?
let fileURL = NSBundle.mainBundle().URLForResource("Input", withExtension: "txt")
let content = String(contentsOfURL: fileURL!, encoding: NSUTF8StringEncoding, error: &error)
On Xcode 7 and Swift 2:
let fileURL = NSBundle.mainBundle().URLForResource("Input", withExtension: "txt")
let content = try String(contentsOfURL: fileURL!, encoding: NSUTF8StringEncoding)
On Xcode 8 and Swift 3:
let fileURL = Bundle.main.url(forResource: "Input", withExtension: "txt")
let content = try String(contentsOf: fileURL!, encoding: String.Encoding.utf8)
If the file has binary data, you can use NSData(contentsOfURL: fileURL!) or Data(contentsOf: fileURL!) (for Swift 3).
While the answer has been supplied for a quick fix, there is a better solution.
Each time the playground is opened it will be assigned a new container. This means using the normal directory structure you would have to copy the file you want into the new container every time.
Instead, inside the container there is a symbolic link to a Shared Playground Data directory (/Users/UserName/Documents/Shared Playground Data) which remains when reopening the playground, and can be accessed from multiple playgrounds.
You can use XCPlayground to access this shared folder.
import XCPlayground
let path = XCPlaygroundSharedDataDirectoryURL.appendingPathComponent("foo.txt")
The official documentation can be found here: XCPlayground Module Reference
Cool post on how to organize this directory per-playground: Swift, Playgrounds, and XCPlayground
UPDATE: For swift 4.2 use playgroundSharedDataDirectory. Don't need to import anything.
Looks like:
let path = playgroundSharedDataDirectory.appendingPathComponent("file")
1. Access a file that is located in the Resources folder of your Playground
With Swift 3, Bundle has a method called url(forResource:withExtension:). url(forResource:withExtension:) has the following declaration:
func url(forResource name: String?, withExtension ext: String?) -> URL?
Returns the file URL for the resource identified by the specified name and file extension.
You can use url(forResource:withExtension:) in order to read the content of a json file located in the Resources folder of an iOS or Mac Playground:
import Foundation
do {
guard let fileUrl = Bundle.main.url(forResource: "Data", withExtension: "json") else { fatalError() }
let data = try Data(contentsOf: fileUrl)
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
You can use url(forResource:withExtension:) in order to read the content of a text file located in the Resources folder of an iOS or Mac Playground:
import Foundation
do {
guard let fileUrl = Bundle.main.url(forResource: "Text", withExtension: "txt") else { fatalError() }
let text = try String(contentsOf: fileUrl, encoding: String.Encoding.utf8)
print(text)
} catch {
print(error)
}
As an alternative to let image = UIImage(named: "image"), you can use url(forResource:withExtension:) in order to access an image located in the Resources folder of an iOS Playground:
import UIKit
do {
guard let fileUrl = Bundle.main.url(forResource: "Image", withExtension: "png") else { fatalError() }
let data = try Data(contentsOf: fileUrl)
let image = UIImage(data: data)
} catch {
print(error)
}
2. Access a file that is located in the ~/Documents/Shared Playground Data folder of your computer
With Swift 3, PlaygroundSupport module provides a global constant called playgroundSharedDataDirectory. playgroundSharedDataDirectory has the following declaration:
let playgroundSharedDataDirectory: URL
The path to the directory containing data shared between all playgrounds.
You can use playgroundSharedDataDirectory in order to read the content of a json file located in the ~/Documents/Shared Playground Data folder of your computer from an iOS or Mac Playground:
import Foundation
import PlaygroundSupport
do {
let fileUrl = PlaygroundSupport.playgroundSharedDataDirectory.appendingPathComponent("Data.json")
let data = try Data(contentsOf: fileUrl)
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
You can use playgroundSharedDataDirectory in order to read the content of a text file located in the ~/Documents/Shared Playground Data folder of your computer from an iOS or Mac Playground:
import Foundation
import PlaygroundSupport
do {
let fileUrl = PlaygroundSupport.playgroundSharedDataDirectory.appendingPathComponent("Text.txt")
let text = try String(contentsOf: fileUrl, encoding: String.Encoding.utf8)
print(text)
} catch {
print(error)
}
You can use playgroundSharedDataDirectory in order to access an image located in the ~/Documents/Shared Playground Data folder of your computer from an iOS Playground:
import UIKit
import PlaygroundSupport
do {
let fileUrl = PlaygroundSupport.playgroundSharedDataDirectory.appendingPathComponent("Image.png")
let data = try Data(contentsOf: fileUrl)
let image = UIImage(data: data)
} catch {
print(error)
}
Swift 3 (Xcode 8)
The code below works in both iOS and macOS playgrounds. The text file ("MyText.txt" in this example) must be in the Resources directory of the playground. (Note: You may need to open the navigator window to see the directory structure of your playground.)
import Foundation
if let fileURL = Bundle.main.url(forResource:"MyText", withExtension: "txt")
{
do {
let contents = try String(contentsOf: fileURL, encoding: String.Encoding.utf8)
print(contents)
} catch {
print("Error: \(error.localizedDescription)")
}
} else {
print("No such file URL.")
}
This works for me. The only thing I changed was to be explicit about the file name (which is implied in your example) - perhaps you have a typo in the off-screen definition of the "file" variable?
let dirs = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as? [String]
let file = "trial.txt" // My change to your code - yours is presumably set off-screen
if let directories = dirs {
let dir = directories[0]; //documents directory
let path = dir.stringByAppendingPathComponent(file);
//read
let content = NSString(contentsOfFile: path, usedEncoding: nil, error: nil)
// works...
}
Update Swift 4.2
As #raistlin points out, this would now be
let dirs = NSSearchPathForDirectoriesInDomains(
FileManager.SearchPathDirectory.documentDirectory,
FileManager.SearchPathDomainMask.userDomainMask,
true)
or, more tersely:
let dirs = NSSearchPathForDirectoriesInDomains(.documentDirectory,
.userDomainMask, true)
Select the .playground file.
Open Utility inspector, In the playground press opt-cmd-1 to open the File Inspector. You should see the playground on the right. If you don't have it selected, press cmd-1 to open the Project Navigator and click on the playground file.
Under 'Resource Path' in Playground Settings choose 'Relative To Playground' and platform as OSX.
On Mavericks with Xcode 6.0.1 you can read using iOS platform too.
import UIKit
let dirs : [String]? = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true) as? [String]
let myDir = "/Shared Playground Data"
let file = "README.md" // My change to your code - yours is presumably set off-screen
if (dirs != nil) {
let directories:[String] = dirs!;
let dir = directories[0] + myDir; // iOS playground documents directory
let path = dir.stringByAppendingPathComponent(file);
//read
let content = String.stringWithContentsOfFile(path, encoding: NSUTF8StringEncoding, error: nil)
// works...
println(content!)
}
Remember, you need to create a directory called "Shared Playground Data" in your Documents directory. Im my case I used this command: mkdir "/Users/joao_parana/Documents/Shared Playground Data" and put there my file README.md
String.stringWithContentsOfFile is DEPRECATED and doesn't work anymore with Xcode 6.1.1
Create your documentDirectoryUrl
let documentDirectoryUrl = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first! as NSURL
To make sure the file is located there you can use the finder command Go To Folder e copy paste the printed documentDirectoryUrl.path there
println(documentDirectoryUrl.path!)
// should look like this: /Users/userName/Library/Containers/com.apple.dt.playground.stub.OSX.PLAYGROUNDFILENAME-5AF5B25D-D0D1-4B51-A297-00015EE97F13/Data/Documents
Just append the file name to the folder url as a path component
let fileNameUrl = documentDirectoryUrl.URLByAppendingPathComponent("ReadMe.txt")
var fileOpenError:NSError?
Check if the file exists before attempting to open it
if NSFileManager.defaultManager().fileExistsAtPath(fileNameUrl.path!) {
if let fileContent = String(contentsOfURL: fileNameUrl, encoding: NSUTF8StringEncoding, error: &fileOpenError) {
println(fileContent) // prints ReadMe.txt contents if successful
} else {
if let fileOpenError = fileOpenError {
println(fileOpenError) // Error Domain=NSCocoaErrorDomain Code=XXX "The file “ReadMe.txt” couldn’t be opened because...."
}
}
} else {
println("file not found")
}
I was unable to read a file with ease in playground and ended up just creating a command line app in Xcode. This seemed to work for me very well.
The other answers, relying on "playgroundSharedDataDirectory" never works for me, especially if using an iOS playground.
let documentsDirectoryShareURL = PlaygroundSupport.playgroundSharedDataDirectory.absoluteURL
let fileManager = FileManager()
try? fileManager.copyItem(at: URL(fileURLWithPath: "/Users/rufus/Documents/Shared Playground Data/"), to: documentsDirectoryShareURL)
I just do the above now. I can populate my documents/shared folder, and it is just manually automatically copied to the playgrounds documents directory.
My code will not overwrite files that exist there. You could enhance this if you need it to look at file timestamps and then copy if necessary etc.
Swift 5.7.1 - Xcode 14.1
func readFile() -> [String] {
if let fileURL = Bundle.main.url(forResource: "File", withExtension: "txt") {
do {
let content = try String(contentsOf: fileURL)
var x = content.components(separatedBy: "\n")
x.removeAll { data in
data.isEmpty
}
return x
} catch {
print(error)
}
}
return [String]()
}
//Usage:
let input = readFile()