swift - How to make Add to Favorites with NSURL array - ios

I'm trying to build an app stores specific sounds which user can play. I assure you I've searched for 2 days but I couldn't what I look for. (Or I couldn't successful with those tuts idk)
The app stores sounds like this:
var sound1 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "Adios Amigos", ofType: "mp3")!)
var sound2 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "ses2", ofType: "mp3")!)
var sound3 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "ses3", ofType: "mp3")!)
var sound4 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "ses4", ofType: "mp3")!)
var soundArray: [NSURL] = [sound1, sound2, sound3, sound4]
here is my button that play sounds as random:
#IBAction func Rastgele(sender: Any) {
let randNo = Int(arc4random_uniform(UInt32(soundArray.count))) // 0...ArrayCount
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
try audioPlayer = AVAudioPlayer(contentsOf: (soundArray[randNo] as NSURL) as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
self.sesLabel.text = soundArray[randNo].deletingPathExtension?.lastPathComponent
soundIndex = randNo} catch {
print(error)
}
}
and here is my IBAction button that I couldn't fill inside with code.
#IBAction func Favori(_ sender: Any) {
// empty
}
I tried to use NSUserdefaults but I couldn't achieve what I want.
I also tried to pull the array item from my array to insert another array but I faced many problems with because of type of array(NSURL)
So, 'Favori' button should store as Favorites then I can show favorited sounds in other table view.
Thanks in advance.
EDIT
I found a solution using Core Data and converting NSURLs to URL. Thanks to the #rmaddy for helpful comment. The working code is down below;
#IBAction func Favori(_ sender: Any) {
// save core data
let app = UIApplication.shared.delegate as! AppDelegate
let context = app.persistentContainer.viewContext
let newSound = NSEntityDescription.entity(forEntityName: "Sounds", in: context)
let sound = NSManagedObject(entity: newSound!, insertInto: context)
sound.setValue(soundArray[soundIndex].deletingPathExtension().lastPathComponent, forKey: "soundName")
do { try context.save()
soundItems.append(sound)
print(sound)
}
catch
{
print("error")
}
}

When you click on the button, save the URL into core data or a local data base using sqlite or a server.
If you just want to pass it to another tableview without saving it (data will lost when you kill the app), you can pass that around using a delegate or notification center.

Related

How to add a delay to a sound being played with swift

I am a total swift beginner and am working on a tutorial project (magic 8 ball) and have been successful at doing the following:
- play a specific sound when the "Ask" button is pressed.
- play a specific sound when for each of the randomly picked images
However now the sound that should play whenever the button is pressed only plays once and from there on i only hear the sounds that are being displayed with each image. Is this because I am "stuck" in the "if - else" loop ? Or do I have to delay the sounds that are being played for each image in the array ?
Thanks so much for your help !
Here is my code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var magicBall: UIImageView!
var magicBallDisplay = 1
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
magicBall.image = #imageLiteral(resourceName: "theanswerisyes")
}
#IBAction func askButtonPressed(_ sender: UIButton) {
let magicBallArray = [ #imageLiteral(resourceName: "askagainlater"),#imageLiteral(resourceName: "no"),#imageLiteral(resourceName: "theanswerisyes"),#imageLiteral(resourceName: "yes"),#imageLiteral(resourceName: "noidea")]
magicBall.image = magicBallArray.randomElement()
let soundURL = NSURL(fileURLWithPath: Bundle.main.path(forResource: "Ask", ofType: "wav")!)
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL as URL)
}catch {
print("there was some error. The error was \(error)")
}
audioPlayer.play()
if (magicBall.image?.isEqual(UIImage(named: "yes")))! {
let soundURL = NSURL(fileURLWithPath: Bundle.main.path(forResource: "yes", ofType: "mp3")!)
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL as URL)
} catch {
print("there was some error. The error was \(error)")
}
audioPlayer.play()
}
else if (magicBall.image?.isEqual(UIImage(named: "no")))! {
let soundURL = NSURL(fileURLWithPath: Bundle.main.path(forResource: "no", ofType: "mp3")!)
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL as URL)
} catch {
print("there was some error. The error was \(error)")
}
audioPlayer.play()
}
else if (magicBall.image?.isEqual(UIImage(named: "theanswerisyes")))! {
let soundURL = NSURL(fileURLWithPath: Bundle.main.path(forResource: "theanswerisyes", ofType: "mp3")!)
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL as URL)
} catch {
print("there was some error. The error was \(error)")
}
audioPlayer.play()
}
else if (magicBall.image?.isEqual(UIImage(named: "noidea")))! {
let soundURL = NSURL(fileURLWithPath: Bundle.main.path(forResource: "noidea", ofType: "mp3")!)
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL as URL)
} catch {
print("there was some error. The error was \(error)")
}
audioPlayer.play()
}
else if (magicBall.image?.isEqual(UIImage(named: "askagainlater")))! {
let soundURL = NSURL(fileURLWithPath: Bundle.main.path(forResource: "askagainlater", ofType: "mp3")!)
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL as URL)
} catch {
print("there was some error. The error was \(error)")
}
audioPlayer.play()
}
}
}
I make a new project and change your code to this:
import UIKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var magicBall: UIImageView!
// you never used this
//var magicBallDisplay = 1
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
magicBall.image = #imageLiteral(resourceName: "theanswerisyes")
}
#IBAction func askButtonPressed(_ sender: UIButton) {
// because you have audio file and image with equal name i made array of string
let magicBallArray = [ "yes","no","theanswerisyes","noidea","askagainlater"]
// check if i get not null item
guard let choosedImageName = magicBallArray.randomElement() else {return}
print(choosedImageName)
// set image with random picked name
magicBall.image = UIImage(named: choosedImageName)
// play ask sound
playSound(fileName: "Ask", fileType: "wav")
// play picked image sound after 10 second from now()
// change number to your needed time
DispatchQueue.main.asyncAfter(deadline: .now() + 10.0, execute: {
self.playSound(fileName: choosedImageName, fileType: "mp3")
})
}
private func playSound(fileName: String, fileType: String)
{
// check if you find the audio file
guard let url = Bundle.main.path(forResource: fileName, ofType: fileType) else {
print("path not found")
return
}
// make NSURL from path
let soundURL = NSURL(fileURLWithPath: url)
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL as URL)
} catch {
print("there was some error. The error was \(error)")
}
audioPlayer.play()
}
}
I explain code for you.
I didn't enable the button. you can improve this code when you came stronger in swift
your code is really messy.
at first, I advise you to make a function for playing sound like that:
func playSound(fileName: String, fileType: String) {
let soundURL = NSURL(fileURLWithPath: Bundle.main.path(forResource: fileName, ofType: fileType)!)
do{
audioPlayer = try AVAudioPlayer(contentsOf: soundURL as URL)
}catch {
print("there was some error. The error was \(error)")
}
audioPlayer.play()
}
you must have a callback for first play sound. after finishing the first sound you can check your if-else or you can try switch-case.
also, you can use delay for playing second sound

Using AVAudioPlayer with Dynamic URL in Swift 3 causing Thread Errors

I am new to Swift and making an audio app using AVAudioPlayer. I am using a remote URL mp3 file for the audio, and this works when it's static.
For my use case, I want to pull a URL for an mp3 file from a JSON array and then pass it into the AVAudioPlayer to run.
If I move the AVAudioPlayer block into the ViewDidLoad and make the mp3 file a static URL, it will run fine.
Then, when I move this code into my block that extracts an mp3 url from JSON, I can print the URL successfully. But when I pass it into my audio player, problems arise. Here's the code.
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://www.example.com/example.json")
URLSession.shared.dataTask(with:url!, completionHandler: {(data, response, error) in
guard let data = data, error == nil else { return }
let json: Any?
do{
json = try JSONSerialization.jsonObject(with: data, options: [])
}
catch{
return
}
guard let data_list = json as? [[String:Any]] else {
return
}
if let foo = data_list.first(where: {$0["episode"] as? String == "Example Preview"}) {
self.audiotest = (foo["audio"] as? String)!
print(self.audiotest) // this prints
// where i'm passing it into the audio player
if let audioUrl = URL(string: self.audiotest) {
// then lets create your document folder url
let documentsDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
// lets create your destination file url
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
//let url = Bundle.main.url(forResource: destinationUrl, withExtension: "mp3")!
do {
audioPlayer = try AVAudioPlayer(contentsOf: destinationUrl)
} catch let error {
print(error.localizedDescription)
}
} // end player
// ....
Specifically, I get an error Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value when clicking a play button IBAction that is connected to the audio player. Finally, that action function looks like this:
#IBAction func playPod(_ sender: Any) {
audioPlayer.play()
}
Do you know where I'm going wrong? I'm confused as to why I can't print the URL and also get a response that the URL is nil in the same block, but maybe that's an asynchronous thing.
The problem is that you didn't save the mp3 file to documents and trying to play it
this line
audioPlayer = try AVAudioPlayer(contentsOf: destinationUrl)
assumes that there is a saved mp3 file in that path , but acutally there is no files you appended the audio extension on the fly
besides for steaming audio from a remote server, use AVPlayer instead of AVAudioPLayer.
AVPlayer Documentation
Also try this with urls parsed from json
var urlStr = (foo["audio"] as? String)!
self.audiotest = urlStr.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)

fatal error: unexpectedly found nil while unwrapping an Optional value on button click

i am faceing Error in this Error is(fatal error: unexpectedly found nil while unwrapping an Optional value)
var souncClick = AVAudioPlayer()
#IBAction func PlayButton(_ sender: Any) {
do{
soundClick = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Click", ofType: "wam")!))
soundClick.prepareToPlay()
let audioSession = AVAudioSession.sharedInstance()
do{
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
catch{
}
}catch{
print("Error")
}
soundClick.play()
//optionModel.cartCounr = 8
let PlayGame = self.storyboard?.instantiateViewController(withIdentifier: GameSectionViewController) as! GameSelectionVC
// self.addChildViewController(PlayGame)
// self.view.addSubview(PlayGame.view)
self.present(PlayGame, animated: true, completion: nil)
}
There are two possibilities of the crash in this code:
1: It seems like you are passing a key GameSectionViewController but you have to verify you PlayGame object have the instance you can verify by printing before presenting.
let PlayGame = self.storyboard?.instantiateViewController(withIdentifier: GameSectionViewController) as! GameSelectionVC
print(PlayGame)
2: The second possibility is the URL may be nil so you can also verify its printing.
let path = Bundle.main.path(forResource: "Click", ofType: "wam")
print(path)
NOTE: Whenever you are getting nil you have to verify by the printing it or by the breakpoint.
If the code crashes in the line
soundClick = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Click", ofType: "wam")!))
it's a design error, because the file is supposed to be in the bundle at runtime.
Either the sound file with this name does not exist or the file name / extension is spelled wrong.
I guess there is a typo in the file extension (wma vs wam). And why don't you use the URL related API of Bundle?
soundClick = try AVAudioPlayer(contentsOf: Bundle.main.url(forResource: "Click", withExtension: "wma")!)
Make use of if let to unwrap optional values in safe way. Don't forcefully unwrap option. if the value of option is nil then you will get crash. And make sure you are getting proper values from the options.
Add storyboard reference id if you didn't added. And make sure the Click.wam file present inside the bundle. The file is not present then you will the nil while retrieve the file path.
if let url = URL.init(fileURLWithPath: Bundle.main.path(forResource: "Click", ofType: "wam") {
soundClick = try AVAudioPlayer(contentsOf: url))
}
if let PlayGame = self.storyboard?.instantiateViewController(withIdentifier: GameSectionViewController) as? GameSelectionVC {
self.present(PlayGame, animated: true, completion: nil)
}
Please delete your file from project and add again with Option -> copy items if needed and -> create groups and -> target to your project as:
You can also check by select your file in project and in right panel check target membership is selected to your project or not.
Then use
#IBAction func PlayButton(_ sender: Any) {
//Check filepath exits or not.
if let filePaths = Bundle.main.path(forResource: "Click", ofType: "wam") {
do{
souncClick = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: filePaths))
souncClick.prepareToPlay()
let audioSession = AVAudioSession.sharedInstance()
do{
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
catch{
}
}catch{
print("Error")
}
souncClick.play()
//optionModel.cartCounr = 8
let PlayGame = self.storyboard?.instantiateViewController(withIdentifier: GameSectionViewController) as! GameSelectionVC
// self.addChildViewController(PlayGame)
// self.view.addSubview(PlayGame.view)
self.present(PlayGame, animated: true, completion: nil)
}
}
Please also check Resource name same as your file name and Type is also same with file extension and storyboard identifier GameSectionViewController also exists.

Play/Pause Audio Button Swift

I'm new to swift and can't seem to figure this out. I am trying to have one button act as both a play button which loops audio and as a pause button if clicked again. I've tried using an if/else statement but I keep getting this error "fatal error: unexpectedly found nil while unwrapping an Optional value"
The code works without the if/else statement, however, in that case it loops indefinitely without a way to pause it.
Any suggestions?
var hihat16: AVAudioPlayer!
#IBAction func hat16(_ sender: Any) {
if hihat16.isPlaying == false {
let path = Bundle.main.path(forResource: "Hat 017-1.aif", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
let sound = try AVAudioPlayer(contentsOf: url)
hihat16 = sound
sound.play()
hihat16.numberOfLoops = -1
} catch {
// couldn't load file :(
}
}else{ hihat16.stop()}
Picture of the code and error message :
Try this.
Do it in your viewDidLoad
let path = Bundle.main.path(forResource: "Hat 017-1.aif", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
hihat16 = try AVAudioPlayer(contentsOf: url)
hihat16.numberOfLoops = -1
} catch {}
Then in your #IBAction you can do this.
if !hihat16.isPlaying {
hithat16.play()
}
else{ hithat.stop()}

Play different audioplayers in sequence Swift

So basically I have two audio players while using the AVFoundation. I want the second one to be played right after the first one finishes.
myFilePathString = NSBundle.mainBundle().pathForResource("\(workout[currentExercise])", ofType: "mp3")
#IBAction func startWorkout(sender: AnyObject) {
if let myFilePathString = myFilePathString {
let myFilePathURL = NSURL(fileURLWithPath: myFilePathString)
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: myFilePathURL)
audioPlayer.play()
}catch {
print("Error")
}
}
This above one is to be the first
let path = NSBundle.mainBundle().pathForResource("Rest", ofType: "mp3")
let url = NSURL(fileURLWithPath: path!)
do {
let sound = try AVAudioPlayer(contentsOfURL: url)
secondAudioPlayer = sound
sound.play()
} catch {
print("error")
}
...while this one is to be played after. At the moment they play at the same time. Got any ideas for me?
Thank you in advance.

Resources