There are 2 apps A & B.
App A has a url to open App B.
Soon as App B opens, it has to load 5 view controllers onto the navigation stack which is done by the following code:
let LandingVC = self.storyboard?.instantiateViewControllerWithIdentifier("LandingVC") as! LandingVC
let Dashboard = self.storyboard?.instantiateViewControllerWithIdentifier("Dashboard") as! Dashboard
let PlayerVC = self.storyboard?.instantiateViewControllerWithIdentifier("PlayerVC") as! PlayerVC
let PlayerDetailVC = self.storyboard?.instantiateViewControllerWithIdentifier("PlayerDetailVC") as! PlayerDetailVC
let ScoreReportVC = self.storyboard?.instantiateViewControllerWithIdentifier("ScoreReportVC") as! ScoreReportVC
let viewControllersList = [LandingVC, Dashboard, PlayerVC, PlayerDetailVC, ScoreReportVC]
self.navigationController?.setViewControllers(viewControllersList, animated: false)
From ScoreReportVC I need to be able to set variables on previous ViewControllers so that the user can navigate to previous screens even though they fired the application from another application.
Here is what I have tried: defined a protocol in previous view controllers that are behind ScoreReportVC that are on the stack and inside ScoreReportVC like so:
for viewcontroller in self.navigationController?.viewControllers {
if viewcontroller is PlayerDetailVC {
PlayerDetailVC.delegate = self
}
if viewcontroller is PlayerVC {
PlayerVC = self
}
if viewcontroller is Dashboard {
Dashboard.delegate = self
}
if viewcontroller is LandingVC {
LandingVC.delegate = self
}
}
But the delegates are not getting called. Any help how to correctly pass data to all the ViewControllers on the stack would be greatly appreciated.
For relatable classes we use Delegations. But the classes which are not relatable , for those, we use Notifications. In your case Notifications will be needed for implementation and to pass data from one VC to another VC.
A better approach would be to create/set the variables when the controllers are created.
Related
I have a tabBarController with below hierarchy
TabBarController
Tab 1 -> Contains Navigation Controller(NavController1) -> ViewController1 -has--> ContainerView --contains--> DisplayedViewControllerTab1 (this is my tab1 view controller displayed)
Variable dataForVC1 is in DisplayedViewControllerTab1
When user taps Tab3 (DisplayedViewControllerTab3), I'm trying to get value of dataForVC1 to pass to tab3 viewController
So far I've tried this
In in TabBarController - didSelect method
var data: ModelData?
if let navController = tabBarController.viewControllers?[0] as? NavController1,
let childNavVC = navController.children.first as? ViewController1 {
//Get container view
let conView = childNavVC.containerView. //This is outlet
//Looking for something like this - struck here
if let displayedVC1 = "Container view's VC as? DisplayedViewControllerTab1 {
data = displayedVC1.dataForVC1
}
}
Kindly advice how to achieve this
You don't need to hustle around with container views. In your ViewController1 simply add references to the DisplayedViewControllerTab1 or other vc's you need and then access them directly.
Your VC code would look something like this.
class ViewController1: UIViewController {
//...
var displayedVC1: DisplayedViewControllerTab1?
//...
}
And then your code now:
var data: ModelData?
if let navController = tabBarController.viewControllers?[0] as? NavController1,
let childNavVC = navController.children.first as? ViewController1 {
data = childNavVC.displayedVc1
}
P.S. For convenience, if you have your own TabBarController class you can add all the references you need when the controller is created or simply have computed properties, so you don't need to go deep in the hierarchy all the time.
In your TabbarViewController class:
var myViewController1: MyViewControllerOne? {
return (viewControllers[0] as? NavController)?.children.first as? MyViewControllerOne
}
var myViewController2: MyViewControllerTwo? {
return (viewControllers[1] as? NavController)?.children.first as? MyViewControllerTwo
}
I have been trying to find the solution but none of them available are working for me, Can someone please advise how can I achieve this?
I have set a rootviewcontroller as a view controller(Container Viewcontroller) by using the following code after successful login.
let vc = self.storyboard?.instantiateViewController(withIdentifier: "ContainerViewController") as! ContainerViewController
let appdelegate = UIApplication.shared.delegate as! AppDelegate
appdelegate.window!.rootViewController = vc
Based on a specific notification received, I have defined a custom function which is being called for sure but I want my app user to go back to root automatically regardless of the navigation controller he is currently on. I am using this code
guard let navigationController = UIApplication.shared.keyWindow?.rootViewController as? UINavigationController else { return }
navigationController.popToRootViewController(animated: true)
Flow is Root(VC)->NavigationController1->PushingViewController2->PushingViewController3, suppose user is currently at ViewController3(which is pushed) and I want to take the user back from anotherClass NSOBject which is observing network Status and upon network failure it is calling ABC function but when I add the above navigation controller reference to pop all navigationcontrollers it is not working.
In my Xcode project when a user taps on a notification I want to first send them to a certain item in my tabBar then I want to instantiate a view controller and send an object over to that view controller. I have code the that sends them to the tabBar I want, but I do not know how to instantiate them to the view controller while keeping the tabBar and navigation bar connected to the view controller. All the answers on this require you to change the root view controller and that makes me lose connection to my tabBar and navigation bar when the view controller is called.
A Real Life Example of this: User receives Instagram notification saying "John started following you" -> user taps on notification -> Instagram opens and shows notifications tab -> quickly send user to "John" profile and when the user presses the back button, it sends them back to the notification tab
Should know: The reason why I'm going to a certain tab first is to get that tab's navigation controller because the view controller I'm going to does not have one.
Here's my working code on sending the user to "notifications" tab (I added comments to act like the Instagram example for better understanding):
if let tabbarController = self.window!.rootViewController as? UITabBarController {
tabbarController.selectedViewController = tabbarController.viewControllers?[3] //goes to notifications tab
if type == "follow" { //someone started following current user
//send to user's profile and send the user's id so the app can find all the information of the user
}
}
First of all, you'll to insatiate a TabBarController:
let storyboard = UIStoryboard.init(name: "YourStoryboardName", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "YourTabBarController") as! UITabBarController
And then insatiate all of the viewControllers of TabBarController. If your viewControllers is embedded in to the UINavigationController? If so, you'll to insatiate a Navigation Controller instead:
let first = storyboard.instantiateViewiController(withIdentifier: "YourFirstNavigationController") as! UINavigationController
let second = storyboard.instantiateViewiController(withIdentifier: "YourSecondNavigationController") as! UINavigationController
let third = storyboard.instantiateViewiController(withIdentifier: "YourThirdNavigationController") as! UINavigationController
Also you should instantiate your desired ViewController too:
let desiredVC = storyboard.instantiateViewController(withIdentifier: "desiredVC") as! ExampleDesiredViewController
Make all of the NavigationControllers as viewControllers of TabBarController:
tabBarController.viewControllers = [first, second, third]
And check: It's about your choice.
if tabBarController.selectedViewController == first {
// Option 1: If you want to present
first.present(desiredVC, animated: true, completion: nil)
// Option 2: If you want to push
first.pushViewController(desiredVC, animated. true)
}
Make tabBarController as a rootViewController:
self.window = UIWindow.init(frame: UIScreen.main.bounds)
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
Finally: It's your completed code:
func openViewController() {
let storyboard = UIStoryboard.init(name: "YourStoryboardName", bundle: nil)
let tabBarController = storyboard.instantiateViewController(withIdentifier: "YourTabBarController") as! UITabBarController
let first = storyboard.instantiateViewiController(withIdentifier: "YourFirstNavigationController") as! UINavigationController
let second = storyboard.instantiateViewiController(withIdentifier: "YourSecondNavigationController") as! UINavigationController
let third = storyboard.instantiateViewiController(withIdentifier: "YourThirdNavigationController") as! UINavigationController
let desiredVC = storyboard.instantiateViewController(withIdentifier: "desiredVC") as! ExampleDesiredViewController
tabBarController.viewControllers = [first, second, third]
if tabBarController.selectedViewController == first {
// Option 1: If you want to present
first.present(desiredVC, animated: true, completion: nil)
// Option 2: If you want to push
first.pushViewController(desiredVC, animated. true)
}
self.window = UIWindow.init(frame: UIScreen.main.bounds)
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
}
If you want to present or push ViewController when the notification is tapped? Try something like that:
extension AppDelegate: UNUserNotificationCenterDelegate {
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
switch response.actionIdentifier {
case UNNotificationDefaultActionIdentifier:
openViewController()
completionHandler()
default:
break;
}
}
}
I can think of two ways to do that:
1) If that view controller is a UINavigationController you can simply push the profile from wherever you are:
if let tabNavigationController = tabbarController.viewControllers?[3] as? UINavigationController {
tabbarController.selectedViewController = tabNavigationController
let profileViewController = ProfileViewController(...)
// ... set up the profile by setting the user id or whatever you need to do ...
tabNavigationController.push(profileViewController, animated: true) // animated or not, your choice ;)
}
2) Alternatively, what I like to do is control such things directly from my view controller subclass (in this case, PostListViewController). I have this helper method in a swift file that I include in all of my projects:
extension UIViewController {
var containedViewController: UIViewController {
if let navController = self as? UINavigationController, let first = navController.viewControllers.first {
return first
}
return self
}
}
Then I would do this to push the new view controller:
if let tabViewController = tabbarController.selectedViewController {
tabbarController.selectedViewController = tabViewController
if let postListViewController = tabViewController.containedViewController as? PostListViewController {
postListViewController.goToProfile(for: user) // you need to get the user reference from somewhere first
}
}
In my last live project, I'm using the same approach like yours. So even though I doubt this method is the correct or ideal for handling a push notification from the AppDelegate (I still got a lot of stuff to learn in iOS 🙂), I'm still sharing it because it worked for me and well I believe the code is still readable and quite clean.
The key is to know the levels or stacks of your screens. The what are childViewControllers, the topMost screen, the one the is in the bottom, etc...
Then if you're now ready to push to a certain screen, you would need of course the navigationController of the current screen you're in.
For instance, this code block is from my project's AppDelegate:
func handleDeeplinkedJobId(_ jobIdInt: Int) {
// Check if user is in Auth or in Jobs
if let currentRootViewController = UIApplication.shared.keyWindow!.rootViewController,
let presentedViewController = currentRootViewController.presentedViewController {
if presentedViewController is BaseTabBarController {
if let baseTabBarController = presentedViewController as? BaseTabBarController,
let tabIndex = TabIndex(rawValue: baseTabBarController.selectedIndex) {
switch tabIndex {
case .jobsTab:
....
....
if let jobsTabNavCon = baseTabBarController.viewControllers?.first,
let firstScreen = jobsTabNavCon.childViewControllers.first,
let topMostScreen = jobsTabNavCon.childViewControllers.last {
...
...
So as you can see, I know the hierarchy of the screens, and by using this knowledge as well as some patience in checking if I'm in the right screen by using breakpoints and printobject (po), I get the correct reference. Lastly, in the code above, I have the topMostScreen reference, and I can use that screen's navigationController to push to a new screen if I want to.
Hope this helps!
EDIT: I was off base in my approach. I'm trying to pass data from one VC to another. The source VC is not controlled by a Tab Bar, so I can't reference the destination VC through it. I am attempting to reference the destination VC thorough the Storyboard, but the data is still nil on the destination VC. I'm using Core Data and wish to pass the ManagedObjectContext to another VC. Here is the code:
......
do {
try self.managedObjectContext.save()
appUserID = user.userID!
} catch {
fatalError("Error: \(error)")
}
}
}
self.passManagedObjectContext()
}
func passManagedObjectContext() {
let profileTableViewController = UIStoryboard(name: "Profile", bundle: nil).instantiateViewController(withIdentifier: "ProfileViewController") as! ProfileTableViewController
profileTableViewController.managedObjectContext = self.managedObjectContext
}
I am passing data from the AppDelegate to other ViewControllers by referencing my TabBarViewControllers stack like so:
let tabBarController = window!.rootViewController as! UITabBarController
if let tabBarViewControllers = tabBarController.viewControllers {
let profileNavController = tabBarViewControllers[4] as! UINavigationController
let profileTableViewController = profileNavController.topViewController as! ProfileTableViewController
profileTableViewController.managedObjectContext = context
}
}
.....
END EDIT
I'm trying to do the same now but from one ViewController to another that is accessed through the tab bar (no segue). The code above does not recognize window!.rootViewController how can I reference the root UITabBarController?
UIViewController has a built-in optional property tabBarController, so inside your view controller you can access the tabBarController like so:
if let tabBarController = tabBarController {
//get the controller you need from
//tabBarController.viewControllers
//and do whatever you need
}
Here is the current list of properties available in the 'Getting Other Related View Controllers' section of Apple's UIViewController documentation:
*Disclaimer: I've only been coding in iOS/XCode/Swift for a couple of weeks
I have Universal Links working in that clicking a link outside my app opens up the app and I catching the Url in the AppDelegate class.
So that's all good.
My question is... how do I then redirect to the correct UIViewController AND pass the controller some info from my URL? All Universal Link tutorials stop before they get to that part.
In particular I'm confused about the lifecycles of AppDelegate and how it relates to UIViewController.
My app had two UIViewController sitting under (is this right?) a UINavigationController.
What I've tried
I have tried handling the url event in AppDelegate, and setting a public property, and then in my ViewController getting access to the AppDelegate. HOWEVER, after the Universal Link is clicked, both viewDidLoad and viewWillAppear don't get called again :-/
What's the best way to redirect to a ViewController from AppDelegate? My goal is simply to load the root view controller BUT I need to pass in some data from the URL. How?
First, Read your URL.
Get your parameters from URL
Initiate your target controller
Set your parameter to that controller
Present controller on root view controller
let urlString = url.absoluteString
let queryArray = urlString.componentsSeparatedByString("/")
if queryArray[2].lowercaseString == "yourQuery" {
let queryId = Int(queryArray[3])
if self.window?.rootViewController?.presentedViewController != nil {
self.window?.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
}
let queryVC = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(QUERY_SCENE) as? QueryViewController
queryVC?.urlQueryId = queryId!
self.window?.rootViewController?.presentViewController(queryVC!, animated: true, completion: nil)
}
Edit:
Push a controller say 'PresentedViewController' on navigation controller and if rootViewController is also navigation controller
And on back press on controller 'OnBackPressViewController' present controller 'PresentedViewController'
if self.window?.rootViewController?.presentedViewController != nil {
self.window?.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
}
let navController = self.window?.rootViewController?.storyboard?.instantiateInitialViewController() as? UINavigationController
let presentedVC = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(PRESENTED_SCENE) as? PresentedViewController
//Pass parameters to PresentedViewController
let onBackPressViewController = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier(ON_BACK_PRESS_SCENE) as? OnBackPressViewController
navController!.pushViewController(onBackPressViewController!, animated: true)
navController!.pushViewController(presentedVC!, animated: true)
self.window?.rootViewController?.presentViewController(navController!, animated: true, completion: nil)
You can update your code according to that. Always remember that you have to present any view controller on rootViewController.
It depends on your app architecture. You can definitely assume that the AppDelegate has the reference to the rootViewController (window?.rootViewController). You should know what's the type of that controller. Then you can access to the other and so on. This question is, anyway, too much generic in my opinion.