1

Currently I use this method to get the current view controller:

func topMostContoller()-> UIViewController?{
    if !Thread.current.isMainThread{
        logError(message: "ACCESSING TOP MOST CONTROLLER OUTSIDE OF MAIN THREAD")
        return nil
    }

    let topMostVC:UIViewController = UIApplication.shared.keyWindow!.rootViewController!
    return topVCWithRootVC(topMostVC)
}

This method goes through the hierarchy of the view controllers starting at the rootViewController until it reaches the top.

func topVCWithRootVC(_ rootVC:UIViewController)->UIViewController?{
    if rootVC is UITabBarController{
        let tabBarController:UITabBarController = rootVC as! UITabBarController
        if let selectVC = tabBarController.selectedViewController{
            return topVCWithRootVC(selectVC)
        }else{
            return nil
        }
    }else if rootVC.presentedViewController != nil{
        if let presentedViewController = rootVC.presentedViewController! as UIViewController!{
            return topVCWithRootVC(presentedViewController)
        }else{
            return nil
        }
    } else {
        return rootVC
    }
}

This issue is in topMostController since it uses UIApplication.shared.keyWindow and UIApplication.shared.keyWindow.rootViewController which should not be used in a background thread. And I get these warning:

runtime: UI API called from background thread: UIWindow.rootViewController must be used from main thread only

runtime: UI API called from background thread: UIApplication.keyWindow must be used from main thread only

So my question is. Is there a thread safe way to access the currently displayed view controller?

boidkan
  • 4,691
  • 5
  • 29
  • 43
  • Add `return `nil` just after `logError...`. But the proper solution is to fix your code so you only call `topMostController` from the main queue. – rmaddy Oct 12 '17 at 20:43

1 Answers1

1

Will accessing from the main thread suit your needs?

func getTopThreadSafe(completion: @escaping(UIViewController?) -> Void) {
    DispatchQueue.main.async {
        let topMostVC: UIViewController = UIApplication.shared.keyWindow?.rootViewController?
        completion(topMostVC)
    }
}

this can get a little bit confusing, since it's an asynchronous method, but my gut tells me that this'd be the safest option with whatever you're up to :)

MQLN
  • 2,292
  • 2
  • 18
  • 33
  • Thanks for the answer. Yea I have done something similar to this where it is possible. What I wanted to do was avoid mass refactoring if there was a reasonable solution. But it looks like I will just have to do something this. I',m gonna let the question sit a little bit longer. Not looking hopeful though. – boidkan Oct 12 '17 at 21:33
  • Absolutely, best of luck. – MQLN Oct 12 '17 at 22:19