如何在ViewController类中以编程方式更改视图控制器

时间:2017-04-06 19:45:24

标签: ios swift uiviewcontroller segue

我知道这个问题已经被无数次询问了,我看到了许多变化,包括

func performSegue(withIdentifier identifier: String, 
       sender: Any?)

以及此处提到的所有其他变体:How to call a View Controller programmatically

但是如何更改ViewController类之外的视图控制器?例如,用户当前在ViewController_A,当蓝牙设备断开连接(超出范围,弱信号等)时,didDisconnectPeripheral CBCentral方法被触发。在同一方法中,我想将当前视图更改为ViewController_B,但此方法不会出现在ViewController类中,因此performSegue等方法将无效。

我在我的AppDelegate中实现了一个似乎有用的建议(过去常常为iphone屏幕大小抓取适当的故事板文件/我非常讨厌AutoLayout

    var storyboard: UIStoryboard = self.grabStoryboard()
    display storyboard
    self.window!.rootViewController = storyboard.instantiateInitialViewController()
    self.window!.makeKeyAndVisible()

然后我尝试在我的非ViewController

中做同样的事情
    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()

然而,我得到了一个异常,说fatal error: unexpectedly found nil while unwrapping an Optional value可能来自window!

对我能做什么以及正确的设计模式有什么建议?

2 个答案:

答案 0 :(得分:1)

试试这个:

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是处理蓝牙设备状态的类。在VC_A中启动它并适当地响应委托方法。这应该是您正在寻找的设计模式。

答案 1 :(得分:0)

我更喜欢dvdblk的解决方案,但我不确定如何实现DispatchQueue.main.async(我在Swift中仍然很新)。所以这是我的迂回,低效的解决方案:

didDisconnectPeripheral singleton我有一个boolean attribute viewdidload,表示无论什么时候会断开连接。

在我ViewController的{​​{1}}中,我会运行scheduledTimer函数,定期检查boolean attribute的状态。随后,在我的viewWillDisappear中,我使计时器无效。