Cannot manipulate AVAudioPlayer located on different ViewController - swift

I have got two ViewControllers. One is "ViewController", the second is "SecondViewController".
On the ViewController, I have var player = AVAudioPlayer(). I have loaded and played BGM there successfully, and then I pushed the SecondViewController into view.
On the SecondViewController, I used this code to stop the audio player on the ViewController:
let viewController = self.storyboard?.instantiateViewControllerWithIdentifier("ViewController") as? ViewController
viewController?.player.pause()
But I get error Thread 1: EXC_BAD_ACCESS (code=1, address=0x38)
From what I learned from browsing around, this happened because I tried to access the player while the player hasn't been initialized yet.
But how it can be? Hasn't it been initialized already? The music is still playing up to when this error occurs. What's wrong and how to fix it so that I can manipulate audio player from a different ViewController than the currently active one (but it's already active in background)? Thanks.

You are instantiating a new view controller; not using the same view controller that presented SecondViewController and so AudioPlayer that gets created is not same as the previous AudioPlayer. You should somehow refer to the presenting view controller.
One way to do would be by what is called a delegate pattern,
class ViewController: UIViewController {
...
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SecondViewController" {
if let secondViewController = segue.destinationViewController as? SecondViewController {
secondViewController.delegate = self
}
}
}
// MARK: SecondViewControllerDelegate
func stopPlayer() {
player.stopPlayer()
}
}
protocol SecondViewControllerDelegate {
func stopPlayer()
}
class SecondViewController: UIViewController {
...
var delegate: SecondViewControllerDelegate?
func stopPlayer() {
delegate?.stopPlayer()
}
}
Or, then, you can also pass AVPlayer from first view controller to SecondViewController and use it to stop from SecondViewController itself,
class ViewController: UIViewController {
...
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "SecondViewController" {
if let secondViewController = segue.destinationViewController as? SecondViewController {
secondViewController.player = self.player
}
}
}
}
class SecondViewController: UIViewController {
...
var player: AVPlayer!
func stop() {
player.stop()
}
}

instantiateViewControllerWithIdentifier creates a new instance of your view controller.
Instead put a property on secondViewController of type YourViewController and set that in the prepareForSegue in YourViewController.

The problem is that instantiateViewControllerWithIdentifier creates a new ViewController. It doesn't return the one that you've been on. Anyway, trying to access something on a previous view controller doesn't seem like a good design to me.
One option is to pass AVAudioPlayer to your second VC in prepareForSegue method. You can declare an AVAudioPlayer property and set it there.

Related

How to call perform segue method from another class?

I want to perform segue from View B to View C and I am calling method in class A.
My segue method in Class B is-
//ViewController B
func nextViewAction() -> Void {
self.performSegueWithIdentifier("nextview", sender: self)
}
And I am calling it in Class A like this-
//ViewController A
#IBAction func sideMenuAction(sender: AnyObject) {
ViewClassB().nextViewAction()
}
But it crashing- Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver () has no segue with identifier 'nextview''
Have you set the name nextview for the segue between Controller B and Controller C? You can check by clicking on the segue between the two controllers, and then checking the Identifier value in the Attributes Inspector.
What you can have is move to the third viewcontroller from first viewcontroller by skipping the second one use the code below..
class MyViewController: UIViewController, UINavigationControllerDelegate {
var viewControllerToInsertBelow : UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
func pushTwoViewControllers() {
if let viewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("id1"),
let viewController3 = self.storyboard?.instantiateViewControllerWithIdentifier("id2") { //change this to your identifiers
self.viewControllerToInsertBelow = viewController2
self.navigationController?.pushViewController(viewController3, animated: true)
}
}
//MARK: - UINavigationControllerDelegate
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if let vc = viewControllerToInsertBelow {
viewControllerToInsertBelow = nil
let index = navigationController.viewControllers.indexOf(viewController)!
navigationController.viewControllers.insert(vc, atIndex: index)
}
}
}
Reference : SO Post
Don't need to do any other code just use
notification observer it will solve your problem.
Thank you
I think you cannot trigger segue action from AViewController because what
performSegue does is that :
Initiates the segue with the specified identifier from the current view controller's storyboard file.
Try implementing segue from A to C

Swift: Switch between NSViewController inside Container View / NSView

I want to achieve a really simple task—changing the ViewController of a Container View by pressing a button:
In my example the ViewController1 is embedded into the Container View using Interface Builder. By pressing the Button ViewController2 I want to change the view to the second ViewController.
I’m confused because the Container View itself seems to be a NSView if I create an Outlet and as far as I know a NSView can’t contain a VC. Really appreciate your help!
Just note that in order for this to work you have to add storyboard identifiers to your view controllers, which can by going to your storyboard then selecting the Identity Inspector in the right hand pane and then entering the Storyboard ID in the Identity subcategory.
Then this implementation of ViewController would achieve what you are looking for.
import Cocoa
class ViewController: NSViewController {
// link to the NSView Container
#IBOutlet weak var container : NSView!
var vc1 : ViewController1!
var vc2 : ViewController2!
var vc1Active : Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Make sure to set your storyboard identiefiers on ViewController1 and ViewController2
vc1 = NSStoryboard(name: "name", bundle: nil).instantiateController(withIdentifier: "ViewController1") as! ViewController1
vc2 = NSStoryboard(name: "name", bundle: nil).instantiateController(withIdentifier: "ViewController2") as! ViewController2
self.addChild(vc1)
self.addChild(vc2)
vc1.view.frame = self.container.bounds
self.container.addSubview(vc1.view)
vc1Active = true
}
// You can link this action to both buttons
#IBAction func switchViews(sender: NSButton) {
for sView in self.container.subviews {
sView.removeFromSuperview()
}
if vc1Active == true {
vc1Active = false
vc2.view.frame = self.container.bounds
self.container.addSubview(vc2.view)
} else {
vc1Active = true
vc1.view.frame = self.container.bounds
self.container.addSubview(vc1.view)
}
}
}
maybe this is a late answer but I will post my solution anyways. Hope it helps someone.
I embedded NSTabViewController in ContainerView. Then, in order not to see tabs on the top I did this:
go to NSTabViewController in storyboard
in Attributes inspector change style to be Unspecified
then click on TabView in Tab Bar View Controller, and set style to be "tabless":
After this you need to:
store tabViewController reference to mainViewController in order to select tabs from code
add a button to mainViewController (where your container is) with which you will change tabs in tabViewController.
You do this by storing the reference to tabViewController when overriding prepare for segue function. Here is my code:
first add property to the mainViewController
private weak var tabViewController: NSTabViewController?
then override this function and keep the reference to tabViewController:
override func prepare(for segue: NSStoryboardSegue, sender: Any?) {
guard let tabViewController = segue.destinationController
as? NSTabViewController else { return }
**self.tabViewController = tabViewController as? NSTabViewController**
}
After this you will have reference to tabViewController all set up.
Next (last) thing you have to do is make an action for button in order to move to first (or second) view controller, like this:
#IBAction func changeToSecondTab(_ sender: Any) {
self.tabViewController?.selectedTabViewItemIndex = 0 // or 1 for second VC
}
All the best!

How do I pass data from a View controller into my pop up view controller (swift/ios)

I'm quite new with Swift and I'm making this mini game type app that counts the score and updates the label in the view controller. I want to pass that score from a view controller into another external pop up view controller I created.
#IBAction func Button7Tapped(_ sender: AnyObject)
{
if Index == 13 {
game.score += 1
} else {
let scorepopVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "finalScorePop") as! finalScoreViewController
self.addChildViewController(scorepopVC)
scorepopVC.view.frame = self.view.frame
self.view.addSubview(scorepopVC.view)
scorepopVC.didMove(toParentViewController: self)
}
updateGame()
}
Above is my code for the external pop up view controller I created, which also has a separated .swift file. How would I go about taking my game.score and passing that into my Popup view controller?
In your finalScoreViewController swift file add a new property.
final class FinalScoreViewController: UIViewController {
var score: Int?
}
And then just assign it when you're instantiating it.
#IBAction func Button7Tapped(_ sender: AnyObject) {
if Index == 13 {
game.score += 1
} else {
let scorepopVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "finalScorePop") as! finalScoreViewController
scorepopVC.score = game.score //THIS LINE
self.addChildViewController(scorepopVC)
scorepopVC.view.frame = self.view.frame
self.view.addSubview(scorepopVC.view)
scorepopVC.didMove(toParentViewController: self)
}
updateGame()
}
It is better to use storyboard to open the ViewController. In storyboard, right click and drag from you button to the second view controller (the one that you wish to open).
Choose the segue type that you wish to use. In your case, I think Present Modally will work fine.
You will see a line between the two UIViewControllers in storyboard. That is the segue. Tap on it. In the Attributes inspector give the segue an identifier. For instance "myFirstSegue".
Then in the code of the UIViewController that contains your button override prepare(for:sender:). This method is called when preparing for the segue to happen. I.o.w when you tap on the button. You have access to the destination UIViewController and can therefor access and set the properties on it.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "myFirstSegue" {
if let vc = segue.destination as? MyViewController {
//here you set your data on the destination view controller
vc.myString = "Hello World"
}
}
}
Note that we check the identifier, because all segues that go from this ViewController to other ViewControllers will call prepare(for:sender:)
It's quite simple, Just add a property in your finalScoreViewController (if you are not already done this) and -for example- call it score:
class finalScoreViewController: UIViewController {
var score: String?
// ...
Add this line to the Button7Tapped action (where you set a value for finalScoreViewController's score):
let scorepopVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "finalScorePop") as! finalScoreViewController
// add this line:
scorepopVC.score = "My score"
self.addChildViewController(scorepopVC)
scorepopVC.view.frame = self.view.frame
self.view.addSubview(scorepopVC.view)
scorepopVC.didMove(toParentViewController: self)
Finally, in finalScoreViewController:
override func viewDidLoad() {
super.viewDidLoad()
if let scr = score {
print(scr)
}
}
Hope that helped.
You do not actually have to pass the variable to the next view controller. All you have to do is create a variable outside of the View Controller class, and voila, you can access your variable from anywhere, in any swift file. For example:
var score = 0
class ViewController: UIViewController {
override func viewDidLoad(){
super.viewDidLoad()
}
#IBAction func Button7Tapped(_ sender: AnyObject){
score += 1
}
}
And then in the other View Controller, you would have something like this:
#IBOutlet weak var scoreLabel: UILabel!
class ViewController: UIViewController {
override func viewDidLoad(){
super.viewDidLoad()
var timer1 = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateScore), userInfo: nil, repeats: true)
}
#objc func updateScore() {
scoreLabel.text = "You have \(score) points!"
}

prepareForSegue when embedding Tab Bar Controller into Navigation Controller

I currently have a navigation controller setup like this:
and my prepareForSegue, that passes data between the initial view (Login View Controller) and the Navigation controller looks like such:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepareForSegue(segue, sender: sender)
let navVc = segue.destinationViewController as! UINavigationController // 1
let chatVc = navVc.viewControllers.first as! ChatViewController // 2
chatVc.senderId = userID // 3
chatVc.senderDisplayName = "" // 4
}
However, when I try to embed in a Tab Bar controller (to add more pages/functionality to my app) like this...
...and run my application, my program crashes at the line let navVc = segue.destinationViewController as! UINavigationController
I know that the problem is that after my initial view, it goes to the tab bar which is type UITabBarController rather than UINavigationController however if I change it, my data does not go to the view that I want it to go to...it is kind of confusing.
Please let me know if you have any ideas how to implement this, or if you have any questions feel free to ask me for clarification.
Thanks!
P.s. The error that I am receiving in the console is:
Could not cast value of type 'UITabBarController' (0x10b8e48b0) to 'UINavigationController' (0x10b8e4860).
Try this:
Start by casting destinationViewController to UITabBarController and then using the viewControllers property to access the first viewController in the tabBarController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let tabVc = segue.destinationViewController as! UITabBarController
let navVc = tabVc.viewControllers!.first as! UINavigationController
let chatVc = navVc.viewControllers.first as! ChatViewController
chatVc.senderId = userID
chatVc.senderDisplayName = ""
}
In the CS193p class, Paul Hegarty shows the uses of extensions to deal with Navigation Controller segues (Lecture 8: 23'). The UIViewController extension introduces a new computed property: contentViewController available to all UIViewControllers (and subclasses).
The code I posted below was adapted to work with TabBarViewControllers as well.
When you are attempting to cast your navigation controller as your ChatViewController, the segue.destinationController.contentViewController will recursively return the ChatViewController.
class LoginViewController: UIViewController {
/* ... */
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let chatVc = segue.destinationViewController.contentViewController as? ChatViewController {
chatVc.senderId = userID
chatVc.senderDisplayName = ""
}
}
}
extension UIViewController {
var contentViewController: UIViewController {
if let navcon = self as? UINavigationController {
return navcon.visibleViewController ?? self
} else if let tabcon = self as? UITabBarController {
return tabcon.selectedViewController ?? self
} else {
return self
}
}
}
A clean way of doing this would be that right after you log in, you use something like:
let controller = self.storyboard?.instantiateViewControllerWithIdentifier("setthisisyourstoryboard") as! UITabBarController // This would instantiate the TabBarController.
let navInstance = controller[0] as! UINavigationController // This would instantiate the navigationController, that is placed at 0th index in the array of all view controllers that are child to TabBarController, since you only have one child, you can use 0 as index
if navInstance.viewControllers[0] is YourClassName { // YouClassName is the name of the class right next to the navigation view controller, and it is also the only child of navigation Controller(0 index)
// You can also send some data here (for example the sender id)
(navInstance.viewControllers[0] as! YourClassName).someProperty = Value
}
// This line would present the tabBar controller, that would ultimately reach the end of the stack. I have used this approach in many apps, it works great!
self.presentViewController(controller, animated: true, completion: nil)
You can delete the segue after this and set storyboard id for the tabbarcontroller.
I like to do it this way because it is more natural. What I mean is the UITabBarController is mostly a parent to View Controllers, I don't like the idea of setting a UITabBarController as a child to UIView Controller.
Maybe there is nothing wrong with it, but I don't prefer it.
Here. it's working pretty fine for me:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let tabVC = segue.destination as? UITabBarController {
if let navVC = tabVC.viewControllers!.first as? UINavigationController {
if let nextVC = navVC.viewControllers.first as? NextVC {
nextVC.varName = "works like a charm"
}
}
}
}
NextVC is your target VC which you want to send your variable into.

How to bring value from a UIviewController using delegate without instantiating object

I have 3 UIViewControllers say:
v1ViewController
v2ViewController
v3ViewController.
I have pushed controllers as v2 on v1 and v3 on v2.
Now I want to bring some value back to v1ViewController from v3ViewController using delegates.
On v3Viewcontroller I wrote it:
for vc in self.navigationController!.viewControllers{
if vc is v1ViewController{
delegate?.returnFilteredImage(imageView.image!)
self.navigationController?.popToViewController(vc, animated: true)
}
}
How can I use delegates because in v1ViewController I haven't create object of v3Viewcontroller; consequently I cannot connect delegate to self.
So how can i do that.
1. Using delgates
create a protocol and implement in your firstviewcontroller
protocol My {
func returnFilteredImage(image: UIImage)
}
class FirstViewController: UIViewcontroller, My {
...
func returnFilteredImage(image: UIImage) {
}
}
and in your thirdViewController create a property and assign FirstViewController delegate to this.
class ThirdViewController: UIViewController {
var delegate: My?
...
override func viewDidLoad() {
super.viewDidLoad()
for vc in self.navigationController!.viewControllers{
if vc is FirstViewController {
let vc1 = vc as! FirstViewController
self.delegate = vc1
self.delegate?.returnFilteredImage(imageView.image!)
self.navigationController?.popToViewController(vc, animated: true)
}
}
}
}
2. Using local notifications
check here
You can do this using local notifications
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//add observer
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.didgetImage(_:)), name: "receiveImageNotification", object: nil)
}
func didgetImage(notification: NSNotification) {
if let image = notification.userInfo?["image"] as? UIImage {
// do something with your image
}
}
}
and from third view controller, first notification
let imageDataDict:[String: UIImage] = ["image": image]
NSNotificationCenter.defaultCenter().postNotificationName("receiveImageNotification", object: self, userInfo: imageDataDict)
Hope this helps :)