Reputation: 123
I know this question has been asked countless times already, and I've seen many variations including
func performSegue(withIdentifier identifier: String,
sender: Any?)
and all these other variations mentioned here: How to call a View Controller programmatically
but how would you change a view controller outside of a ViewController
class? For example, a user is currently on ViewController_A
, when a bluetooth device has been disconnected (out of range, weak signal, etc) the didDisconnectPeripheral
method of CBCentral
gets triggered. In that same method, I want to change current view to ViewController_B
, however this method doesn't occur in a ViewController
class, so methods like performSegue
won't work.
One suggestion I've implemented in my AppDelegate
that seems to work (used to grab the appropriate storyboard file for the iphone screen size / I hate AutoLayout
with so much passion)
var storyboard: UIStoryboard = self.grabStoryboard()
display storyboard
self.window!.rootViewController = storyboard.instantiateInitialViewController()
self.window!.makeKeyAndVisible()
And then I tried to do the same in my non-ViewController
class
var window: UIWindow?
var storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil) //assume this is the same storyboard pulled in `AppDelegate`
self.window!.rootViewController = storyboard.instantiateViewController(withIdentifier: "ViewController_B")
self.window!.makeKeyAndVisible()
However I get an exception thrown saying fatal error: unexpectedly found nil while unwrapping an Optional value
presumably from the window!
Any suggestions on what I can do, and what the correct design pattern is?
Upvotes: 1
Views: 532
Reputation: 2955
Try this:
protocol BTDeviceDelegate {
func deviceDidDisconnect()
func deviceDidConnect()
}
class YourClassWhichIsNotAViewController {
weak var deviceDelegate: BTDeviceDelegate?
func yourMethod() {
deviceDelegate?.deviceDidDisconnect()
}
}
class ViewController_A {
var deviceManager: YourClassWhichIsNotAViewController?
override func viewDidLoad() {
deviceManager = YourClassWhichIsNotAViewController()
deviceManager.delegate = self
}
}
extension ViewController_A: BTDeviceDelegate {
func deviceDidDisconnect() {
DispatchQueue.main.async {
// change the VC however you want here :)
// updated answer with 2 examples.
// The DispatchQueue.main.async is used here because you always want to do UI related stuff on the main queue
// and I am fairly certain that yourMethod is going to get called from a background queue because it is handling
// the status of your BT device which is usually done in the background...
// There are numerous ways to change your current VC so the decision is up to your liking / use-case.
// 1. If you are using a storyboard - create a segue from VC_A to VC_B with an identifier and use it in your code like this
performSegue(withIdentifier: "YourSegueIdentifierWhichYouveSpecifiedInYourSeguesAttibutesInspector", sender: nil)
// 2. Instantiate your VC_B from a XIB file which you've created in your project. You could think of a XIB file as a
// mini-storyboard made for one controller only. The nibName argument is the file's name.
let viewControllerB = ViewControllerB(nibName: "VC_B", bundle: nil)
// This presents the VC_B modally
present(viewControllerB, animated: true, completion: nil)
}
}
func deviceDidConnect() {}
}
YourClassWhichIsNotAViewController
is the class which handles the bluetooth device status. Initiate it inside the VC_A and respond to the delegate methods appropriately. This should be the design pattern you are looking for.
Upvotes: 1
Reputation: 123
I prefer dvdblk's solution, but I wasn't sure how to implement DispatchQueue.main.async
(I'm still pretty new at Swift). So this is my roundabout, inefficient solution:
In my didDisconnectPeripheral
I have a singleton
with a boolean attribute
that would signify whenever there would be a disconnect.
In my viewdidload
of my ViewController
I would run a scheduledTimer
function that would periodically check the state of the boolean attribute
. Subsequently, in my viewWillDisappear
I invalidated the timer.
Upvotes: 0