In have a UITabBarController with several UIViewControllers. Inside one of the controllers, when a certain condition is met, I want to instantiate another UIViewController which is a child of the same UITabBarController.
I keep getting this error "Application tried to present modally an active controller", but I don't understand how is scheduleNavController already active. I looked up several answers on SO, but I still don't understand what is my mistake and how can I solve it?
The flow of the app is like this: WelcomeViewController,LoginViewController,UITabBarController and the children of the UITabBarController.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabController = storyboard.instantiateViewController(withIdentifier: "CentralTabBarControllerID") as! UITabBarController
if let viewControllers = tabBarController?.viewControllers {
let scheduleNavController = viewControllers[1] as! UINavigationController
let scheduleVC = scheduleNavController.childViewControllers[0] as! Schedule
tabController.present(scheduleNavController, animated: true, completion: {
scheduleVC.segmentedControlIndexReceivedFromClaimDetail = self.segmentedControlIndex
})
}
Thanks to Andreas Oetjen suggestion to use the selectedIndex of the UITabBarController I have come up with another solution. However, I still don't know exactly how I should fix my code from the original question to make it work.
//select index of the UIViewController we want to switch to
// get the UINavigationController for the tab we want to switch to
// get UIViewController to which we want to pass data
self.tabBarController?.selectedIndex = 1
let scheduleNavController = tabBarController?.viewControllers?[1] as! UINavigationController
let scheduleVC = scheduleNavController.childViewControllers[0] as! Schedule
scheduleVC.segmentedControlIndexReceivedFromClaimDetail = self.segmentedControlIndex
//remove the the current UIViewController from which we switch to another controller above.
let claimDetailNav = tabBarController?.viewControllers?[0] as! UINavigationController
let claimDetailVC = claimDetailNav.childViewControllers[1] as! ClaimDetail
claimDetailVC.removeFromParentViewController()
Related
I have an app that has 4 tab bar items (A, B, C, D). I'm using notification data to direct a user to a child vc of A ('A-child') and I want to populate the search bar in A-child with a value from the notification.
I'm successfully getting the notification and value, that's fine. I'm also able to navigate to A just fine using:
let tabBarController = UIApplication.shared.keyWindow?.rootViewController as! UITabBarController
tabBarController.selectedIndex = 0
But I then need to navigate to A-Child VC from A and then set the search bar text (I'm less worried about the search bar text piece as I think I can work that out once I get the right VC stack in place). I could of course use a segue to go straight from where the user when they tap on the notification to A-child, but then I lose the expected navigation behaviour for the user from A-child.
I know I'm not the first to ask a question like this, and I've gone through everything I can find on SO relating to this - but can't make any of the answers click. Help is much appreciated!
Edit:
I've got it partially working with this:
if let tabbarController = UIApplication.shared.keyWindow?.rootViewController as?
UITabBarController {
let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "AChildViewController") as!
AChildViewController
vc.searchString = "text"
tabarController.present(vc, animated: true, completion: nil)
}
Not sure if this is an appropriate way or I'm going to get myself in trouble
It isn't being presented inside the navigation controller - so I'm not getting the top nav bar (including the critical search bar)
It isn't being presented inside the tab bar controller
when I tried to do tabBarController.navigationController? < the navigation controller is nil
EDIT 2 - Solution:
Found an unaccepted answer from a couple of years ago that did the trick for me via: https://stackoverflow.com/a/51763243/12481584
let tabBar: UITabBarController // get your tab bar
tabBar.selectedIndex = 0 // eg. zero. To be sure that you are on correct tab
if let navigation = tabBar.viewControllers?[tabBar.selectedIndex] as? UINavigationController {
let storyboard = UIStoryboard.init(name: "Main", bundle: Bundle.main)
if let chatViewController = storyboard.instantiateViewController(withIdentifier: "chatViewController") as? ChatViewController {
navigation.pushViewController(chatViewController, animated: true)
}
}
The question is not so clear, but I assume your problem is basically how to navigate to a child controller of TAB A from anywhere.
There are multiple ways to do this (deep-linking), but the most straight forward way literally just do your usual approach of pushing, popping, presenting, dismissing of controllers and combine local storage of your data that indicates where you should redirect the user to after tapping a push notification or deep-linking from anywhere such as a website.
An extension of getting the current or top most screen should help, for instance, the is how I do it:
import UIKit
var windowRootController: UIViewController? {
if #available(iOS 13.0, *) {
let windowScene = UIApplication.shared
.connectedScenes
.filter { $0.activationState == .foregroundActive }
.first
if let window = windowScene as? UIWindowScene {
return window.windows.last?.rootViewController
}
return UIApplication.shared.windows.filter {$0.isKeyWindow}.first?.rootViewController
} else {
return UIApplication.shared.keyWindow?.rootViewController
}
}
/// Category for any controller.
extension UIViewController {
/// Class function to get the current or top most screen.
class func current(controller: UIViewController? = windowRootController) -> UIViewController? {
guard let controller = controller else { return nil }
if let navigationController = controller as? UINavigationController {
return current(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return current(controller: selected)
}
}
if let presented = controller.presentedViewController {
return current(controller: presented)
}
return controller
}
}
Now, onto your specific problem. So assuming you really now handle the redirection to TAB A properly, the next thing you would do is push the Child A after going to the TAB A, and then in the Child A didAppear, put the text in the searchBar and do the searching.
Im trying to open a certain view from a push notification but i keep losing the nav bar and the back and next references. this what my storyboard looks like this (with the view i want to open)
this is what i have in my AppDelagate:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController
self.window?.rootViewController = vc
What you're doing is completely replacing the root view controller of your application, this means that all the current UI will be discarded.
What you should do instead is to use your knowledge of your application to direct it to the new content. For example, if your root view controller is a navigation controller, you can cast rootViewController to a nav controller and push it (this will fail if your root view controller is something else, like a tab bar controller).
guard let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController else {
fatalError("Main Storyboard doesn't have a notification controller")
}
guard let nav = self.window?.rootViewController as? UINavigationController else {
return //handle unexpected state
}
nav.push(vc, animated: true)
Another option would be to embed your notification controller into a navigation controller, add a Close button, and present it modally, that way you can present it on top of rootViewController no matter what that controller is.
As we can see in the screenshot your provided, the application's root view controller is the UINavigationController instance.
And according to that, let me offer the next code:
func handleNotification(){
let storyboard = UIStoryboard(name: "Main", bundle: nil)
guard let vc = storyboard.instantiateViewController(withIdentifier: "notification") as? NotificationViewController else{
debugPrint("NotificationViewController with identifier 'notification' not found")
return
}
guard let navVC = self.window?.rootViewController as? UINavigationController else{
debugPrint("RootViewController is not an UINavigationController")
return
}
navVC.pushViewController(vc, animated: true) //perhaps your will prefer to use false
}
Beside that, you can use more flexible implementation.
In your AppDelegate post a (NS)Notification when notification intercepted, the relevant view-controller(s) observe the notification, and act when notification broadcasted.
You can also set an identifier to the segue and invoke performSegue method from the observing view-controller
You can set from storyboard -> add view controller -> Embed in navigation controller -> set second view controller -> Attach seque between that controllers. You will see same view controllers like that image .
I have a background task that runs from AppDelegate, when it needs to it displays notifications.
When these notifications are tapped they should direct the user to the ViewController that relates to the notification.
I was wondering if it was possible to perform segues from AppDelagate.
My ViewControllers are in a navigation controller. I'm guessing I have to instantiate my root view controller and perform segue there, just not sure how to do that from appdelegate.
Edit:
Here is my code so far, it works it just isn't embedded in my navigation controller
if let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ConversationVC") as? conversationTableViewController {
if let window = self.window, let rootViewController = window.rootViewController {
var currentController = rootViewController
while let presentedController = currentController.presentedViewController {
currentController = presentedController
}
currentController.present(controller, animated: true, completion: nil)
}
}
You can't segue from a storyboard that isn't loaded yet.
Depending on your case you could:
1: Send data from the AppDelegate to the initial root view controller of your current storyboard and from there, create multiple segues to the respectable VC depending on the data.
OR
2: Create multiple storyboards (one for each case) and launch the appropriate ones from the AppDelegate according to the notification.
Edit: Need to see the storyboard. For the variable, add it to conversationTableViewController and set it up just before you present it:
controller.myVar = "someValue"
self.window.rootViewController = controller
self.window.makeKeyAndVisible()
I want to send a user to a specific ViewController in my app once a notification is clicked.
I now that I can do something like this:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewControllerWithIdentifier("Home") as? HomeViewController
presentedVC?.presentViewController(destinationViewController!, animated: true, completion: nil)
But my app has a tab bar and looks like this
Tab bar
tab1: navigationController -> VC1
tab2: navigationController -> VC2 -> HomeVC
tab: navigationController -> VC3
Each tab has a navigationController as a infront of it.
So how can I send the user to HomeVC? I must first select tab 2 then the navigation controller then push the user tvice:
tab2: navigationController -> VC2 -> HomeVC
And the other problem, if there any way to tell if the user is already in HomeVC? I dont want to send the user to the same VC if his already there.
You must have access to your UITabbarController in you UIApplicationDelegate or wherever you're handling the notification tap.
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController //or whatever your way of getting reference is
So first you'll get the reference to UINavigationController in your second tab like this:
let navInTab:UINavigationController = tabBar.viewControllers?[1] as! UINavigationController
Now push your home view at second tab's navigation controller:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let destinationViewController = storyboard.instantiateViewControllerWithIdentifier("Home") as? HomeViewController
navInTab.pushViewController(destinationViewController!, animated: true)
And finally switch your tab to second to show the just pushed home controller
tabBar.selectedIndex = 1
Keep in mind, this answer assumes that your application has already set the tab bar as the root view controller of application window prior handling the notification tap.
Try something like this:
if let tabBarController = window?.rootViewController as? UITabBarController {
tabBarController.selectedIndex = 1 // in your case the second tab
}
The idea is to switch to get the tab bar instance and switch it to your desired tab (where you have your view controller).
The above code works in AppDelegate / you can easily call it anywhere by getting the tabBarController instance.
You can check which tab is selected by user with the method var selectedIndex: Int. You can check which view controller is present like this self.navigationController?.presentingViewController?.presentedViewController. This will solve your problem.
There is a great blog post over at http://www.appcoda.com/tag/swrevealviewcontroller/ that goes in to setting up SWRevealViewController which is a great component for slide out side menus (https://github.com/John-Lluch/SWRevealViewController)
Unfortunately, there are no swift examples of how to perform a manual segue.
Took a cleaner approach to storyboard support. SWRevealViewControllerSegue is now deprecated and you should use SWRevealViewControllerSegueSetController and SWRevealViewControllerSeguePushController instead.
I've tried something along the lines of:
let navigationController = self.window?.rootViewController as! SWRevealViewController;
let viewController = navigationController.storyboard?.instantiateViewControllerWithIdentifier("ImportFileSelect") as! ImportFileSelect
navigationController.showViewController(viewController, sender: self)
This doesn't work though. Any ideas? I've trawled the web for swift examples, my next step is to learn objective c!
In order to work you'll need to following steps:
You need to instantiate the SWRevealViewController and then attach it
to the root controller.
Then instantiate the destination controller.
Then create a navigation controller and set the destination
controller as the rootViewController
Finally push the navigation controller with SWReveal
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let sw = storyboard.instantiateViewControllerWithIdentifier("SWRevealViewController") as! SWRevealViewController
self.view.window?.rootViewController = sw
let destinationController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardID") as! NameOfViewController
let navigationController = UINavigationController(rootViewController: destinationController)
sw.pushFrontViewController(navigationController, animated: true)
I've kind of made some progress. I can load in a new view controller, but it doesn't animate in anyway. The code to do this, on a button click is:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("TARGET_VIEW_CONTROLLER") as! UIViewController
var rvc:SWRevealViewController = self.revealViewController() as SWRevealViewController
rvc.pushFrontViewController(vc, animated: true)
Download SWRevealViewController project's zip form Github.
Drag SWRevealViewController.m and SWRevealViewController.h files from zip (SWRevealViewController folder) to your project, and Click “Yes” to the prompted message "Would you like to configure an Objective-C bridging header?"
Take a look at storyboard in RevealControllerStoryboardExample2 project. Design your storyboard with this example.
This is not how it should be done, but at least i found a way to do it.
In the TableViewController where u have the slideOut menu do something like:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if(indexPath.row == <some position> ){
let vc: AnyObject! = self.storyboard?.instantiateViewControllerWithIdentifier("YOUR_DESTINATION_IDENTIFIER")
self.showViewController(vc as! SWRevealViewController, sender: vc)
}
The one that satisfy the condition will segue with default animation, not like the slide out menu normally does.
After, in storyboard, do a normal Reveal View Controller Push controller, and as long as it doesn't satisfy the condition it will exit the slide out menu normally