Open ViewController (which is a navigationController within a tabBarController) and run function - ios

I want to allow the user to open an image up in my app from their email. I have set up functions in the app delegate as follows to navigate to my settingsTableViewController. The settingsTableViewController is a navigationController and that is a tabBarViewController.
func application(app: UIApplication, openURL url: NSURL,
options: [String : AnyObject]) -> Bool {
self.window!.visibleViewController
goToSecond()
return true
}
func goToSecond() {
let tabBarController: UITabBarController = (self.window?.rootViewController as? UITabBarController)!
tabBarController.selectedIndex = 3
}
The extension I used to make the above work was:
public extension UIWindow {
public var visibleViewController: UIViewController? {
return UIWindow.getVisibleViewControllerFrom(self.rootViewController)
}
public static func getVisibleViewControllerFrom(vc: UIViewController?) -> UIViewController? {
if let nc = vc as? UINavigationController {
return UIWindow.getVisibleViewControllerFrom(nc.visibleViewController)
} else if let tc = vc as? UITabBarController {
return UIWindow.getVisibleViewControllerFrom(tc.selectedViewController)
} else {
if let pvc = vc?.presentedViewController {
return UIWindow.getVisibleViewControllerFrom(pvc)
} else {
return vc
}
}
}
}
The above works until I add my code in to pass the url to the settingsTableViewController as follows:
let vc = self.window?.rootViewController as! SettingsTableViewController
vc.displayImage(url)
I get the following error:
Could not cast value of type '.TabBarController' (0x100161c00) to '.SettingsTableViewController'
Any suggestions?

Your error tells you exactly what the problem is. The RVC is actually a TabBarController, not your SettingsTableViewController. This means you need to access the TabBarController's viewController array until you arrive at your SettingsTableViewController

Related

Show detail view controller after spotlight search

in a recent project I implemented the spotlight search and everything works fine (I used a UINavigationController). Now I'm recycling the spotlight search for another project that doesn't contain a UINavigationController but just two UIViewController. Also in this case, everything works fine except the redirection to the detail view controller when a specific item is pressed in the spotlight search. I found out that the problem is in this method and it's because I don't use a UINavigationController. So my question is, how can I change this code to work with only my two UIViewController?
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: #escaping ([Any]?) -> Swift.Void) -> Bool {
let navController = self.window!.rootViewController as! UINavigationController
if let actID = userActivity.userInfo!["kCSSearchableItemActivityIdentifier"] as? String {
let ricette = DataManager.shared.arra.filter { $0.id.uuidString == actID }
if let ricetta = ricette.first {
debugPrint("Abbiamo la ricetta grazie all'ID")
if let listController = navController.topViewController as? ListController {
if let posizione = DataManager.shared.arra.index(of: ricetta) {
listController.showDetailFromSpotlightSearch(posizione)
}
} else if let ricettaController = navController.visibleViewController as? RicettaController {
ricettaController.ricetta = ricetta
ricettaController.aggiornaInterfaccia()
}
} else { debugPrint("errore ID") }
} else { debugPrint("errore ID") }
return true
}
You should use this two extension to determine the topViewController and the visibleViewController:
extension UIApplication {
class func getVisibleViewController(_ rootViewController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let presentedViewController = rootViewController?.presentedViewController {
return getVisibleViewController(presentedViewController)
}
return rootViewController
}}
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}}

How to get the current visible viewController from AppDelegate

So, I'm using the method bellow from UIApplication extension to get the top view controller:
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
But the problem is: It always returns UIViewController. But I need to check if it is MyViewController for example. How do I achieve that?
Do conditional casting on the return value to safely check its type.
if let currentVC = UIApplication.topViewController() as? MyViewController {
//the type of currentVC is MyViewController inside the if statement, use it as you want to
}
Your whole function implementation is flawed, if it actually worked, it would lead to infinite recursion. Once you find out the type of your current top view controller in your if statements, you are calling the same function again with the current root controller as its input value. Your function only ever exists, if it reaches either a call from a view controller, whose class is none of the ones specified in your optional bindings.
Moreover, your whole implementation doesn't do anything at the moment. You find out the type of your root view controller, but than you upcast it by returning a value of type UIViewController.
You can do a conditional check with an if-let statement like this:
if let presented = controller?.presentedViewController as? MyViewController {
// it is a MyViewController
}
You can also just directly check if the UIViewController is that type of class like this:
if controller?.presentedViewController is MyViewController {
// it is a MyViewController
}
Try this:
if let presented = controller?.presentedViewController as? MyViewController {
...
You can check it in following ways
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
else if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
else if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
// Answer
if let topVC = AppDelegate.topViewController() as? MyViewController {
// Here your topVC is MyViewController
}
// or
if let topVC = AppDelegate.topViewController() {
if topVC is MyViewController {
// Here your topVC is MyViewController
}
}
To use the UIViewController as MyViewController:
if let myViewController = UIApplication.topViewController() as? MyViewController { ... }
or if you just want to check that the UIViewController is of type MyViewController:
if UIApplication.topViewController() is MyViewController { ... }

Swapping centreViewControllers with FloatingDrawers

I am using a third party pod KGFloatingDrawer which is great because it achieves this:
and is a reimplementation of JVFloatingDrawer. I used their sample code and the sliding drawers are working great!
BUT
When I first run my app I call one centreViewController with no drawers (Login). Then after login I call a new centreViewController with
appDelegate.centerViewController = appDelegate.navigationBarController()
which only works if I restart the app. Am I missing something?
The logout seems fine though
appDelegate.centerViewController = appDelegate.drawerSettingsViewController()
which puzzles me a bit because then I think I'm on the right track?
Am I supposed to only use normal segues and such first and then only call the drawerViewController?
Here is the other code when setting up the floating drawers :
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = drawerViewController
window?.makeKeyAndVisible()
return true
}
private var _drawerViewController: KGDrawerViewController?
var drawerViewController: KGDrawerViewController {
get {
if let viewController = _drawerViewController {
return viewController
}
return prepareDrawerViewController()
}
}
func prepareDrawerViewController() -> KGDrawerViewController {
let drawerViewController = KGDrawerViewController()
drawerViewController.centerViewController = drawerSettingsViewController()
drawerViewController.leftViewController = leftViewController()
drawerViewController.rightViewController = rightViewController()
drawerViewController.backgroundImage = UIImage(named: "sky3")
_drawerViewController = drawerViewController
return drawerViewController
}
private func drawerStoryboard() -> UIStoryboard {
let storyboard = UIStoryboard(name: StoryboardIDs.MainStoryBoardID , bundle: nil)
return storyboard
}
private func viewControllerForStoryboardId(storyboardId: String) -> UIViewController {
let viewController: UIViewController = drawerStoryboard().instantiateViewControllerWithIdentifier(storyboardId)
return viewController
}
func drawerSettingsViewController() -> UIViewController {
let viewController = viewControllerForStoryboardId(StoryboardIDs.LoginViewConSid)
return viewController
}
func sourcePageViewController() -> UIViewController {
let viewController = viewControllerForStoryboardId(StoryboardIDs.SettingsViewConID)
return viewController
}
func navigationBarController() -> UIViewController{
let viewController = viewControllerForStoryboardId(StoryboardIDs.NavConSid)
return viewController
}
private func leftViewController() -> UIViewController {
let viewController = viewControllerForStoryboardId(StoryboardIDs.LeftViewConID)
return viewController
}
private func rightViewController() -> UIViewController {
let viewController = viewControllerForStoryboardId(StoryboardIDs.RightViewConID)
return viewController
}
func toggleLeftDrawer(sender:AnyObject, animated:Bool) {
_drawerViewController?.toggleDrawer(.Left, animated: animated, complete: { (finished) -> Void in
// do nothing
})
}
func toggleRightDrawer(sender:AnyObject, animated:Bool) {
_drawerViewController?.toggleDrawer(.Right, animated: animated, complete: { (finished) -> Void in
// do nothing
})
}
func closeDrawer(sender:AnyObject, animated:Bool){
_drawerViewController?.closeDrawer(.Left, animated: animated, complete: { (finished) -> Void in
})
}
private var _centerViewController: UIViewController?
var centerViewController: UIViewController {
get {
if let viewController = _centerViewController {
return viewController
}
return drawerSettingsViewController()
}
set {
if let drawerViewController = _drawerViewController {
drawerViewController.closeDrawer(drawerViewController.currentlyOpenedSide, animated: true) { finished in }
if drawerViewController.centerViewController != newValue {
drawerViewController.centerViewController = newValue
}
}
_centerViewController = newValue
}
}
Any help/suggestions would be appreciated :D
Just gonna put this here in case anyone has similar problems.
After a week long struggle to find the problem. I eventually found that whenever I changed the centreViewController with
appDelegate.centerViewController = appDelegate.navigationBarController()
OR
appDelegate.centerViewController = appDelegate.logoutController()
that the methods
deinit {
print("deinit called")
notifCentre.removeObserver(self)
}
were not being called in any of the viewControllers.
So I added the line
self.dismissViewControllerAnimated(false, completion: {})
every time that I change the centreViewController.
Apparently Swift normally deinits automagically but when the using the third party methods there is some confusion with the memory handler and we need to step in. Good to know though as it could be a general swift issue as well.

I have multiple storyboards. How can I use AppDelegate to open another ViewController in another storyboard? (Segue)

Here is the code I have. I have tried a few different approaches and some of them gives me the error that the view is not in the hierarchy.
The code snippet below goes in the correct else but can't perform the segue or presentViewController
func applicationDidTimout(notification: NSNotification) {
if let vc = self.window?.rootViewController as? UINavigationController {
if let myTableViewController = vc.visibleViewController as? AccountsOverviewViewController {
// Call a function defined in your view controller.
myTableViewController.signOffUser()
} else {
// We are not on the main view controller. Here, you could segue to the desired class.
let storyboard = UIStoryboard(name: "Accounts", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("AccountsNavigationController") as UIViewController
let vc2 = getVisibleViewController(nil)
vc2?.presentViewController(vc, animated: true, completion: nil)
}
}
}
func getVisibleViewController(var rootViewController: UIViewController?) -> UIViewController? {
if rootViewController == nil {
rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
}
if rootViewController?.presentedViewController == nil {
return rootViewController
}
if let presented = rootViewController?.presentedViewController {
if presented.isKindOfClass(UINavigationController) {
let navigationController = presented as! UINavigationController
return navigationController.viewControllers.last!
}
if presented.isKindOfClass(UITabBarController) {
let tabBarController = presented as! UITabBarController
return tabBarController.selectedViewController!
}
return getVisibleViewController(presented)
}
return nil
}
Use the func below to get the visible view controller,
func getVisibleVC() -> UIViewController? {
if var visibleVC = window?.rootViewController {
while let presentedVC = visibleVC.presentedViewController {
visibleVC = presentedVC
}
return visibleVC
}
return nil
}

How to get top view controller when UIImagePickerController is presented?

At some time I present UIImagePickerViewController. Once it is presented, I call my function: UIStoryboard.topViewController():
extension UIStoryboard {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let svc = base as? UISplitViewController where svc.viewControllers.count == 1 {
return topViewController(svc.viewControllers[0])
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
}
When I print result, all I get is:
0x000000014cb2aa00
{
UIKit.UIResponder = {...}
}
How to get UIImagePickerController from topViewController() function?
Not a complete answer to your case, but here's how I find the top VC in my program. You should be able to edit it for your case.
class UIHelper {
static func getCurrentViewController() -> UIViewController? {
var currentViewController: UIViewController?
if let window = UIApplication.sharedApplication().delegate?.window {
currentViewController = window!.rootViewController?.presentedViewController
}
if currentViewController == nil {
return nil
}
//Check for my version of my main tab bar VC
if let tabBarController = currentViewController as? RootTabBarController {
currentViewController = tabBarController.selectedViewController
print("Tab bar presents \(currentViewController)")
}
// Check if it's a nav VC
if let navController = currentViewController as? UINavigationController {
currentViewController = navController.viewControllers[0] as? UIViewController
print("Nav controller presents \(currentViewController)")
}
print("Current controller: \(currentViewController)")
return currentViewController
}
}

Resources