I am using AVAudioPlayer for my app, and when the view controller loads the audio plays automatically as the view loads but how do I make a statement if the audio cannot find that file and when it can't find the file I want an alert message to pop up saying the audio cannot be found.
Code:
var euphoriaAudio = try! AVAudioPlayer(contentsOfURL:
NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Euphoria", ofType: "mp3")!))
Note: This variable declaration is not in any function. It is outside sitting alone in the class which works perfectly fine.
Should do everything you described.
class EuphoriaViewController: UIViewController {
var player: AVAudioPlayer?
private func showAlert(message: String) {
let alert = UIAlertController(title: "Warning",
message: message,
preferredStyle: .Alert)
let ok = UIAlertAction(title: "OK", style: .Default) { action in
// Execute some code upon OK tap here if you'd like
}
alert.addAction(ok)
presentViewController(alert, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
guard let fileURL = NSBundle.mainBundle().URLForResource("Euphoria",withExtension: "mp3") else {
showAlert("Can't find Euphoria.mp3 resource")
return
}
do {
player = try AVAudioPlayer(contentsOfURL: fileURL)
player?.prepareToPlay()
}
catch {
showAlert("Can't load Euphoria.mp3 resource")
}
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
player?.play()
}
}
Next time, try it yourself and post questions like - I did this, but it doesn't work, here's the code, ...
If you don't know how to display alert, ... You should start with Start developing iOS Apps Today, About iOS App Architecture, iOS HIG, ...
Also you should read How do I ask a good question to get your questions answered in the future. So far you have 10 questions, some of them answered, 3 of them with accepted answer, ... Try to ask better ...
Related
I am trying to play a mp3 file(5 seconds) when app launched and I keep getting the error:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
import UIKit
import AudioToolbox
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
//create SystemSoundID
var soundID:SystemSoundID = 0
let path = Bundle.main.path(forResource: "iPhone100Voice", ofType: "mp3")
let baseURL = NSURL(fileURLWithPath: path!)
AudioServicesCreateSystemSoundID(baseURL, &soundID)
//play
AudioServicesPlaySystemSound(soundID)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Can anyone explain why and how I can fix this?
Check these points, all conditions must be true:
Is the file in the project navigator?
Is the Target Membership checkbox checked in the File Inspector (to get it select the file and press ⌥⌘1)
Is the file name spelled correctly (iPhone100Voice.mp3)?
Please check your file name, maybe filename is incorrect or not in the bundle
So please check file name with extension.
Use if let or guard to unwrap the path:
override func viewDidLoad() {
super.viewDidLoad()
var soundID:SystemSoundID = 0
if let path = Bundle.main.path(forResource: "iPhone100Voice", ofType: "mp3") {
let baseURL = NSURL(fileURLWithPath: path)
AudioServicesCreateSystemSoundID(baseURL, &soundID)
AudioServicesPlaySystemSound(soundID)
} else {
let alert = UIAlertController(title: "Alert", message: "File not found in your bundle", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
It may be help's to you to play video. While adding video file into project make sure target checkbox selected or not in file inspector.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
if let path = Bundle.main.path(forResource: "video_1523966087401 (1)", ofType: ".mp4"){
let player = AVPlayer(url: URL(fileURLWithPath: path))
let playerController = AVPlayerViewController()
playerController.player = player
present(playerController, animated: true) {
player.play()
}
}else{
//show dialog to user
print("Error: File not found")
}
}
! means crash operator, it unwraps optional value. Value there works as like Optional(53) , for say. So the variable should contain value except nil, when path! tries to unwrap optional and appends value 53 in path variable.
to fix this we can use two ways
Using guard,
guard let pathValue = path else {
return
}
let baseURL = NSURL(fileURLWithPath: pathValue) //no need to use pathValue, if you use then you have to use path!, although it makes no crash now.
AudioServicesCreateSystemSoundID(baseURL, &soundID)
//play
AudioServicesPlaySystemSound(soundID)
if path is nil, it enters the block and return , if it has value , it continues to next line.
using if-let, knowing optional binding
if let pathValue = path {
let baseURL = NSURL(fileURLWithPath: pathValue) //same comment
AudioServicesCreateSystemSoundID(baseURL, &soundID)
//play
AudioServicesPlaySystemSound(soundID)
}
Now, if path is optional , can't reach the block and pass to next line
I have made a custom LaunchScreen in the Main.Storyboard to make it seem that a sound is actually coming from the LaunchScreen. The sound works fine and the segue to the next view controller as well. The only problem is that the segue happens before the sound has stopped playing. I would like the sound to complete before making the segue. Logically, it should work since the performSegue is directly after the .play(). But it seems that the two happens simultaneously. Here's my code:
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "fanfare", ofType: ".mp3")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound.play() //WORKS
//
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "myLaunchSegue", sender: self) //WORKS
}
I have tried to add:
perform(Selector(("showNavController")), with: self, afterDelay: 3)
where "showNavController" simply is the segue:
func showNavController() {
performSegue(withIdentifier: "myLaunchSegue", sender: nil)
}
but the program crashes with the error "uncaught exception.... ....unrecognized selector sent to instance"
I have also tried to add a boolean to keep the program from progressing until the sound has played, but didn't get it to work. Any ideas?
//////////////////////////
Update:
Trying Russels answer, but have a few questions. Setting AVAudioPlayer as delegate, does that mean setting it next to the class like this:
class MyLaunchViewController: UIViewController, AVAudioPlayer { ...
Also, how do I call the function audioPlayerDidFinishPlaying? Like so:
audioPlayerDidFinishPlaying(musicSound, successfully: true)
I'll post the whole code block. Makes it easier to understand...
import UIKit
import AVFoundation //FOR SOUND
class MyLaunchViewController: UIViewController, AVAudioPlayer {
var musicSound: AVAudioPlayer = AVAudioPlayer() //FOR SOUND
override func viewDidLoad() {
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "fanfare", ofType: ".mp3")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound.play() //WORKS
//
audioPlayerDidFinishPlaying(musicSound, successfully: true)
}
optional func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
DispatchQueue.main.async() {
self.performSegue(withIdentifier: "myLaunchSegue", sender: self)
}
}
I get an error when writing AVAudioPlayer in the class (perhaps I misunderstood what I was supposed to do?). It says I have multiple inheritances. Also, it doesn't want me to set the new function as optional, as its only for protocol members. Finally, If I correct the errors and run the program, the next segue runs before the sound has finished playing... :( sad panda.
You need to make your LaunchScreen an AVAudioPlayerDelegate, and then use the audioPlayerDidFinishPlaying callback. Here's all you need in the first controller
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate
{
var musicSound: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
//PLAY SOUND CLIP//
let musicFile = Bundle.main.path(forResource: "sound", ofType: ".wav")
do {
try musicSound = AVAudioPlayer(contentsOf: URL (fileURLWithPath: musicFile!))
} catch { print("ERROR PLAYING MUSIC")}
musicSound?.delegate = self
musicSound!.play() //WORKS
print("Next line after play")
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
DispatchQueue.main.async() {
print("audioPlayerDidFinishPlaying")
self.performSegue(withIdentifier: "myLaunchSegue", sender: self)
}
}
}
You can get more details here https://developer.apple.com/documentation/avfoundation/avaudioplayerdelegate/1389160-audioplayerdidfinishplaying
I have made a simple game using the Game template in Xcode, coded in swift. I created a shapeNode, and when it is touched, I would like this code to run:
if SLComposeViewController.isAvailableForServiceType(SLServiceTypeFacebook){
var controller = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
controller.setInitialText("Testing Posting to Facebook")
//self.presentViewController(controller, animated:true, completion:nil)
}
This code is run in the GameViewController.swift file, but gives this error. This error occurs on the commented line.
Could not cast value of type 'UIView' (0x379480d0) to 'SKView' (0x37227ad0).
Update: If you are targeting iOS 9 or above there are some small changes to make this work. You will need to add the correct URL schemes to your info.plist otherwise the check to see if the app is installed will not work.
NOTE: Its is a better idea to now use UIActivityController for sharing. This allows you to only use 1 button and you can share to all sorts of services.
http://useyourloaf.com/blog/querying-url-schemes-with-canopenurl/
To present a viewController in a SKScene you need to use the rootViewController
self.view?.window?.rootViewController?.presentViewController(...
I use a little helper for this using swift 2 protocol extensions, so you can use it anywhere you like in your app. The Facebook part looks like this, twitter is basically the same.
import SpriteKit
import Social
/// URLString
private struct URLString {
static let iTunesApp = URL(string: "Your iTunes app link")
static let facebookApp = URL(string: "Your Facebook app link")
static let facebookWeb = URL(string: "Your Facebook web link")
}
/// Text strings
private struct TextString {
static let shareSheetText = "Your share sheet text"
static let error = "Error"
static let enableSocial = "Please sign in to your account first"
static let settings = "Settings"
static let ok = "OK"
}
/// Social
protocol Social {}
extension Social where Self: SKScene {
/// Open facebook
func openFacebook() {
guard let facebookApp = URLString.facebookApp else { return }
guard let facebookWeb = URLString.facebookWeb else { return }
if UIApplication.shared.canOpenURL(facebookApp){
UIApplication.shared.openURL(facebookApp)
} else {
UIApplication.shared.openURL(facebookWeb)
}
}
/// Share to facebook
func shareToFacebook() {
guard SLComposeViewController.isAvailable(forServiceType: SLServiceTypeFacebook) else {
showAlert()
return
}
guard let facebookSheet = SLComposeViewController(forServiceType: SLServiceTypeFacebook) else { return }
facebookSheet.completionHandler = { result in
switch result {
case .cancelled:
print("Facebook message cancelled")
break
case .done:
print("Facebook message complete")
break
}
}
let text = TextString.shareSheetText
//facebookSheet.setInitialText(text)
facebookSheet.setInitialText(String.localizedStringWithFormat(text, "add your score property")) // same as line above but with a score property
facebookSheet.addImage(Your UIImage)
facebookSheet.add(URLString.iTunesApp)
self.view?.window?.rootViewController?.present(facebookSheet, animated: true, completion: nil)
}
// MARK: - Private Methods
/// Show alert
private func showAlert() {
let alertController = UIAlertController(title: TextString.error, message: TextString.enableSocial, preferredStyle: .alert)
let okAction = UIAlertAction(title: TextString.ok, style: .cancel) { _ in }
alertController.addAction(okAction)
let settingsAction = UIAlertAction(title: TextString.settings, style: .default) { _ in
if let url = URL(string: UIApplicationOpenSettingsURLString) {
UIApplication.shared.openURL(url)
}
}
alertController.addAction(settingsAction)
self.view?.window?.rootViewController?.present(alertController, animated: true, completion: nil)
}
}
To use the helper you simply go to the SKScene you need to call the methods and implement the protocol
class YourScene: SKScene, Social {....
Now when the Facebook node/button is pressed you can call the methods as if they are part of the scene itself.
openFacebook() // opens app or safari
shareToFacebook() // opens share sheet textField
all thanks to swift 2 and protocol extensions. The cool bit about this is say you want to use this helper in a regular UIKit app, than all you have to do is import UIKit instead of spriteKit
import UIKit
and change the protocol extension to this
extension Social where Self: UIViewController {....
Its quite nice and very flexible I think
Hope this helps.
I have 3 buttons in a VC, all hooked up to IBAction functions. Two of them work fine but the Submit button simply simply won't trigger.
I have made sure User Interaction is enabled. I have also tried adding sender: AnyObject as a parameter and re-hooking up the function to the button but still no luck. I have also cleaned the project. I am very baffled as to what is going on.
Here is how the VC looks:
Hooking the buttons up:
Accessibility of button:
Here is the code for each IBAction func:
#IBAction func captureImage(){
self.saveVideoVar = false
let imageFromSource = UIImagePickerController()
imageFromSource.delegate = self
imageFromSource.allowsEditing = false
//if there is a camera
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.Camera){
imageFromSource.sourceType = UIImagePickerControllerSourceType.Camera
self.presentViewController(imageFromSource, animated: true){}
}
else{
let title = "Error"
let message = "Could not load camera"
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil))
presentViewController(alert, animated: true, completion: nil)
}
}
#IBAction func openImageLibrary(){
self.saveVideoVar = false
let imageFromSource = UIImagePickerController()
imageFromSource.delegate = self
imageFromSource.allowsEditing = false
imageFromSource.sourceType = UIImagePickerControllerSourceType.PhotoLibrary
//presents (loads) the library
self.presentViewController(imageFromSource, animated: true){}
}
//code to submit image and video to amazon S3
#IBAction func submitToS3(){
print("x")
if let img : UIImage = imageView.image! as UIImage{
let path = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent("image.png")
let imageData: NSData = UIImagePNGRepresentation(img)!
imageData.writeToFile(path as String, atomically: true)
// once the image is saved we can use the path to create a local fileurl
let url:NSURL = NSURL(fileURLWithPath: path as String)
nwyt.uploadS3(url)
}
}
Screenshot of control clicking the Submit button:
OH MY GOD! I feel stupid. There was a duplicate screen I had forgotten to delete that looked exactly the same but wasn't the one that was being displayed. I'm going to delete this in an hour. Below was the problem:
Check by setting background colors to the buttons so that you can understand whether any view is over the button or not .
I can see that there is an extra ":" in "submitToS3:", meaning that the function submitToS3 should have an argument, which is not your case.
In order to solve this, just remove the submitToS3 link, and then drag and drop from the Submit button to the yellow icon above in the controller, and link it to the "submitToS3" (you should see it there). When looking back at the Received Actions view, you should not see the ":"
Try this:
//code to submit image and video to amazon S3
#IBAction func submitToS3(sender:UIButton){
print("x")
if let img : UIImage = imageView.image! as UIImage{
let path = (NSTemporaryDirectory() as NSString).stringByAppendingPathComponent("image.png")
let imageData: NSData = UIImagePNGRepresentation(img)!
imageData.writeToFile(path as String, atomically: true)
// once the image is saved we can use the path to create a local fileurl
let url:NSURL = NSURL(fileURLWithPath: path as String)
nwyt.uploadS3(url)
}
}
Seems callback argument was missing. Should work. Finger crossed!!
Please check the enabled property in property inspector!
Check button name!
Create a new button and new method. Try and hook. I had faced this kinda problem. Could be xCode issue if you are using xCode 7.
I have this function, that I bring to life in first scene:
func ThemeSound() {
if let path = NSBundle.mainBundle().pathForResource("InfinityThemeMain", ofType: "wav") {
let enemy1sound = NSURL(fileURLWithPath:path)
println(enemy1sound)
var error:NSError?
ThemePlayer = AVAudioPlayer(contentsOfURL: enemy1sound, error: &error)
ThemePlayer.prepareToPlay()
ThemePlayer.play()
}
}
but the problem is, It plays the theme just once. I need it to be played on repeat. And I thought I knew how to make this happen.
runAction(SKAction.repeatActionForever(SKAction.sequence([SKAction.runBlock(ThemePlayer), SKAction.waitForDuration(2.55)]))
But I can't paste in the ViewController, because runAction needs a child to attach it to.
Any other way I could do it?
Just set the numberOfLoops property to a negative number.
ThemePlayer.numberOfLoops = -1
From the documentation:
numberOfLoops
The number of times a sound will return to the beginning, upon reaching the end, to repeat playback.
...
var numberOfLoops: Int
...
A value of 0, which is the default, means to play the sound once. Set a positive integer value to specify the number of times to return to the start and play again. For example, specifying a value of 1 results in a total of two plays of the sound. Set any negative integer value to loop the sound indefinitely until you call the stop method.
You may also need to make your AVAudioPlayer a property, rather than a local variable. As it stands, with automatic reference counting, I think your local ThemePlayer instance is likely to be deallocated by ARC. Someone who knows AVAudioPlayer better than me might be able to comment on that.
(As an aside, you might want to follow Apple's convention of naming object instances by starting with lowercase letters, e.g. themePlayer rather than ThemePlayer. It'll make your code easier for other people to read.)
I make it repeat by using Timer()
here is my code:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player = AVAudioPlayer()
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func play(_ sender: UIButton) {
// var player = AVAudioPlayer()
let soundURL = Bundle.main.url(forResource: "beeb", withExtension: "wav")
do {
player = try AVAudioPlayer(contentsOf: soundURL!)
} catch {
print("No sound found by URL")
}
alertation(title: "click stop to stop", msg: "")
}
func alertation(title:String,msg:String)
{
player.play()
self.timer = Timer.scheduledTimer(withTimeInterval: 3, repeats: true, block: { (timer) in
self.player.play()
})
var alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
var stop = alert.addAction(UIAlertAction(title: "STOP", style: .default, handler: { (action) in
self.player.stop()
self.timer.invalidate()
}))
self.present(alert, animated: true, completion: nil)
}
}