31

I have already looked in Stackoverflow but I can't get an answer. I want to create function that stop playing the sound in another ViewController. But when I clicked the stop button, it cracked and showed "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)". This is my code.

First ViewController

import UIKit
import AVFoundation

class FirstVC: UIViewController {

   var metronome: AVAudioPlayer!
   override func viewDidLoad() {
       super.viewDidLoad()
   do {
        let resourcePath1 = Bundle.main.path(forResource: "music", ofType: "mp3")
        let url = NSURL(fileURLWithPath: resourcePath1!)
        try metronome = AVAudioPlayer(contentsOf: url as URL)

        metronome.prepareToPlay()
        metronome.play()
    } catch let err as NSError {
        print(err.debugDescription)
    }
}

and another Viewcontroller is

import UIKit
class SecondVC: UIViewController {
   var metronomePlay = FirstVC()

@IBAction func stopBtnPressed(_ sender: Any) {
   metronomePlay.metronome.stop() //"EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)"
   }
}
Moin Shirazi
  • 4,372
  • 2
  • 26
  • 38
Andyopf
  • 441
  • 1
  • 5
  • 12

11 Answers11

28

As of Swift 5+:

Put this in sending controller:

NotificationCenter.default.post(name: Notification.Name(rawValue: "disconnectPaxiSockets"), object: nil)

Put this in receiving controller viewDidLoad() or viewWillAppear():

NotificationCenter.default.addObserver(self, selector: #selector(disconnectPaxiSocket(_:)), name: Notification.Name(rawValue: "disconnectPaxiSockets"), object: nil)

and then the following function in your receiving controller class:

@objc func disconnectPaxiSocket(_ notification: Notification) {
    ridesTimer.invalidate()
    shared.disconnectSockets(socket: self.socket)
}
shanezzar
  • 1,031
  • 13
  • 17
25

Swift 5:

Put this in the Action

NotificationCenter.default.post(name: Notification.Name("NewFunctionName"), object: nil)

Put this in viewdidload() in a different viewcontroller (where is the function you want to use)

NotificationCenter.default.addObserver(self, selector: #selector(functionName), name: Notification.Name("NewFunctionName"), object: nil)

The function

 @objc func functionName (notification: NSNotification){ //add stuff here}

I hope I was helpful

Romy
  • 498
  • 9
  • 14
21

You are creating a NEW copy of FirstVC and calling stop on something that is not yet initialised.

You should really use a delegate in this case, something like

protocol controlsAudio {
   func startAudio()
   func stopAudio()
}

class FirstVC: UIViewController, controlsAudio {
    func startAudio() {}
    func stopAudio() {}

    // later in the code when you present SecondVC
    func displaySecondVC() {
       let vc = SecondVC()
       vc.delegate = self
       self.present(vc, animated: true)
    }

}

class SecondVC: UIViewController {
    var delegate: controlsAudio?

    // to start audio call self.delegate?.startAudio)
    // to stop audio call self.delegate?.stopAudio)

}

So you are passing first VC to the second VC, so when you call these functions you are doing it on the actual FirstVC that is in use, rather than creating a new one.

You could do this without protocols if you like by replacing the var delegate: controlsAudio? with var firstVC: FirstVC? and assigning that, but I wouldn't recommend it

Scriptable
  • 19,402
  • 5
  • 56
  • 72
  • didn't work for me with protocol because delegate in SecondVC comes nil – Marcel Jan 27 '21 at 22:07
  • it looks like it worked for the 19 people that upvoted the answer. are you using weak references? are you setting the delegate properly and holding a reference to the VC? – Scriptable Jan 28 '21 at 15:46
  • @Marcel, I met the same issue. I don't use storyboard, so need to declare `self.delegate = FirstVC()` in SecondVC explicitly, and mostly in `viewDidLoad` method. That fix my issue, otherwise you would get nil as the delegate property is never assigned a value. – Zhou Haibo Jan 29 '21 at 10:48
13

I use this way to call my functions from another viewControllers:

let sendValue = SecondViewController();
sendValue.YourFuncion(data: yourdata);
Minas Petterson
  • 1,159
  • 1
  • 8
  • 6
5

You can call function from other viewControllers in many ways.

Two ways that are already discussed above are by delegates & protocols and by sending notifications.

Another way is by passing closures to your second viewController from firstVC.

Below is the code in which while segueing to SecondVC we pass a closure to stop the metronome. There will be no issue because you are passing the same firstVC (not creating a new instance), so the metronome will not be nil.

class FirstVC: UIViewController {

   var metronome: AVAudioPlayer!
   override func viewDidLoad() {
      super.viewDidLoad()
      do {
           let resourcePath1 = Bundle.main.path(forResource: "music", ofType: "mp3")
           let url = NSURL(fileURLWithPath: resourcePath1!)
           try metronome = AVAudioPlayer(contentsOf: url as URL)

           metronome.prepareToPlay()
           metronome.play()
        } catch let err as NSError {
            print(err.debugDescription)
        }

      let secondVC = SecondVC()
      secondVC.stopMetronome = { [weak self] in
        self?.metronome.stop()
      }
      present(secondVC, animated: true)

    }
}


class SecondVC: UIViewController {
   var metronomePlay = FirstVC()
   var stopMetronome: (() -> Void)? // stopMetronome closure

   @IBAction func stopBtnPressed(_ sender: Any) {
      if let stopMetronome = stopMetronome {
         stopMetronome() // calling the closure
      }

   }

 }
Amyth
  • 383
  • 3
  • 9
3
var metronomePlay = FirstVC()

you are creating a new instance on FirstVC, instead you should perform the function on the same instance that of already loaded FirstVC.

Emel Elias
  • 572
  • 7
  • 18
3

Updating @Scriptable's answer for Swift 4

Step 1 :

Add this code in your view controller, from which you want to press button click to stop sound.

@IBAction func btnStopSound(_ sender: AnyObject)
{
    notificationCenter.post(name: Notification.Name("stopSoundNotification"), object: nil)

}

Step 2:

Now its final step. Now add this below code, to your result view controller, where you want to automatically stop sound.

func functionName (notification: NSNotification) {
           metronomePlay.metronome.stop()
}

override func viewWillAppear(animated: Bool) {
           NSNotificationCenter.defaultCenter().addObserver(self, selector: "functionName",name:"stopSoundNotification", object: nil)

}
Krunal
  • 77,632
  • 48
  • 245
  • 261
Alex Bailey
  • 793
  • 9
  • 20
  • 2
    Note: This is not technically my answer converted to Swift 4. My answer uses the delegate pattern which is used in all Swift versions. This is just a different way of doing it in Swift 4. – Scriptable Apr 12 '18 at 07:40
0

You are initialising metronome in viewDidLoad method of FirstVC. In SecondVC, you are initialising metronomePlay as a stored property, but never asking for ViewController's view and thus viewDidLoad of FirstVC is not getting called which results in metronome(stored property) not getting initialised.

Puneet Sharma
  • 9,369
  • 1
  • 27
  • 33
0

You initialize metronome on FirstVC in viewDidLoad, which won't happen until you load the view of metronomePlay instantiated in SecondVC.

You have to call _ = metronomePlay.view, which will lazily load the view of SecondVC and subsequently execute viewDidLoad, before actually calling metronomePlay.metronome.

bartlomiej.n
  • 500
  • 6
  • 19
-1

Try this in SecondVC. var metronomePlay = FirstVC().metronome

-1

Either use the notification process to stop from anywhere or use same FirstVC instance from SecondVC class.

Zoeb S
  • 695
  • 1
  • 7
  • 21