Passing data through multiple viewcontrollers - swift

I want to pass data from firstViewController -> secondNavigationcontroller -> secondTabBarController -> secondViewcontroller.
I can pass it to my navigationcontroller like this:
if segue.identifier == "StartMatchSegue" {
if let destination = segue.destination as? SecondNavigationController {
destination.delegate = self as? UINavigationControllerDelegate
destination.testString = "lets play"
}
}
but I cant figure out how to pass it all the way.
storyboard

You could also create a singleton which can be accessed within each view controller.

You can pass data in different ways two of them i have mentioned below.
NotitificationCenter
Closure

Related

Swift: Passing a class method as a function parameter (instead of a generic function)

I'm trying to call a function on a root view controller from a popover view controller. The function below works but it seems there should be a better way:
func doSomethingInRoot() {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let currentRoot = appDelegate.window!.rootViewController as? RootViewController {
currentRoot.somefunction()
}
}
In the above code, doSomethingInRoot would be contained in the popover, and someFunction would be a function of RootViewController.
What I'm looking for is a way to pass someFunction to another function that contains the if let above, however, the problem is currentRoot is only assigned as a RootViewController within the above (i.e., doSomethingInRoot doesn't know that it's being passed a method of RootViewController instead of a generic function).
In writing this question, I realized that I could do the following, which feels pretty clean, but I'm still curious whether I can pass a method to a specific class as parameter.
func doSomethingInRoot() {
if let root = getRoot() {
root.someFunction()
}
}
func getRoot() -> RootViewController? {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate,
let currentRoot = appDelegate.window!.rootViewController as? RootViewController {
return currentRoot
} else {
return nil
}
}
In your popover view controller, you could add a property like this to hold the reference to someFunction
var rootsSomeFunction: (()->Void)!
Then assign the reference when creating the popover VC whether it's in prepareForSegue, or after you instantiate if from storyboard...etc. I'd also move getRoot() to the App Delegate.
popoverVC.rootsSomeFunction = appDelegate.getRoot().someFunction().self
Finally, in your popover view controller, you can call it by the property like this:
self.rootsSomeFunction()

Trying to pass information between controllers fails, yet works from AppDelegate to controller

I have two UIViewControllers, vc1, and vc2. vc1 is embedded in a UIViewController which is embedded in a UITabBarController, but vc2 is not embedded in either.
How do I pass information from vc2 to vc1? After a user performs an action the data is saved and vc2 simply closes, so there isn't a segue to pass information. Obviously I can't reference vc1 through the Navigation stack or the TabController.
I could save to the AppDelegate, but I've read this isn't a good practice.
This is the code I use to pass information from AppDelegate to vc1 I tried it in vc2, but obviously it failed.:
let tabBarController = window!.rootViewController as! UITabBarController
if let tabBarViewControllers = tabBarController.viewControllers {
let navPostViewController = tabBarViewControllers[0] as! UINavigationController
let user = User(context: managedObjectContext)
if user.userID != nil {
print("User is loggedIn")
isUserLoggedIn = true
} else {
print("User is not loggedIn")
isUserLoggedIn = false
}
let postViewController = navPostViewController.topViewController as! PostViewController
postViewController.managedObjectContext = managedObjectContext
}
First off, I've never got into the habit of using segue to pass information. What i would recommend is that you implement the delegate pattern whenever you need to pass data between two objects. Its a lot cleaner.
For instance lets say you wanted to pass data between LoginViewController and PostViewController:
protocol LoginViewControllerDelegate:NSObjectProtocol{
func userDidLogin(data:String)
}
class LoginViewController:UIViewController {
weak var delegate:LoginViewControllerDelegate?
...
#IBAction func loginButtonPressed(sender:UIButton) {
//Perform login logic here
//If successful, tell the other controller or the 'delegate'
self.delegate?.userDidLogin(data:"Some data....")
}
}
class PostViewController:UIViewController, LoginViewControllerDelegate {
func userDidLogin(data:String) {
print("Got data from login controller: \(data)")
}
}
//How you might use this
loginViewController.delegate = postViewController
One caveat to remember is to never try to have strong references between two objects i.e. do not have the objects hold onto each other or this will cause a memory leak.

Swift: Accessing current navigation controller from a UICollectionViewCell

I have a UICollectionViewCell class "ProductCell"; I am trying to access the current navigation controller in order to update a barbuttonicon. I have tried the following code as this is what I use in my other UIViewControllers:
let nav = self.navigationController as! MFNavigationController
nav.updateCartBadgeValue()
However it states that the
value of type ProductCell has no member navigationController
I am aware that this is not a UIViewController but surely you should be able to access the current navigation controller the same way?
I also know that you can access the navigation controller by using UIApplication in the following way:
let navigationController = application.windows[0].rootViewController as! UINavigationController
I am not sure if that is a good way of doing it though.
Any help is much appreciated
Thanks
UIResponder chain will help here.
You can search the responder chain to find the controller for any view
extension UIView {
func controller() -> UIViewController? {
if let nextViewControllerResponder = next as? UIViewController {
return nextViewControllerResponder
}
else if let nextViewResponder = next as? UIView {
return nextViewResponder.controller()
}
else {
return nil
}
}
func navigationController() -> UINavigationController? {
if let controller = controller() {
return controller.navigationController
}
else {
return nil
}
}
}
controller() will return the closest responder that is of type UIViewController
Then on the returned controller you just need to find its navigation controller. You can use navigationController() here.
The simplest way is to add a property to you cell class that weakly references a UINavigationController
weak var navigationController: UINavigationController?
you will need to assign it in your cellForRow(atIndexPath:_) method.
let cell = tableView.dequeueReusableCell(withIdentifier: "yourReuseID") as! YourCellClass
cell.navigationController = navigationController //will assign your viewController's navigation controller to the cell
return cell
Unless things change, this is a good way to do it. To give you an example of a messier solution... You could add a
let hostViewController:UIViewController
property to your cell and add an initializer to handle it
let cell = ProductCell(vc: self)
But I don't think that's a better way to do it. your suggestion works fine.
let navigationController = application.windows[0].rootViewController as! UINavigationController

Pass object from tableview to destination with revealviewcontroller in between

I am trying to pass an object from my tableview to the detail view. I am using the revealviewcontroller framework to have a slide out menu. Therefore I need to create a segue from the tableview to the revealviewcontroller and from here another one to the final detailviewcontroller.
That is why I canĀ“t set the object in the detail view - any idea how to do so?
This is the used code:
if segue.identifier == "communityDetailSegue" {
// Get the cell that generated this segue.
if let selectedCommunityCell = sender as ? UITableViewCell {
let destination = segue.destinationViewController as!CommunityViewController
if let communityIndex = self.tableView.indexPathForCell(selectedCommunityCell) {
destination.community = self.communitiesOfCurrentUser[communityIndex.row]
print(self.communitiesOfCurrentUser[communityIndex.row].name)
}
}
}
And this is the exception.
Could not cast value of type 'SWRevealViewController' (0x10027b9f0) to 'CommunityViewController'
You get the error because the segue's destination VC is the SWRevealViewController and not the CommunityViewController.
One way of solving your problem would be to pass the value in two steps:
First, in prepareForSegue() you pass the value to the SWRevealViewController (you'll need a subclass for this one, e.g. MyRevealViewController):
if segue.identifier == "communityDetailSegue" {
// Get the cell that generated this segue.
if let selectedCommunityCell = sender as ? UITableViewCell {
let destination = segue.destinationViewController as! MyRevealViewController
if let communityIndex = self.tableView.indexPathForCell(selectedCommunityCell) {
destination.community = self.communitiesOfCurrentUser[communityIndex.row]
print(self.communitiesOfCurrentUser[communityIndex.row].name)
}
}
}
Then, in MyRevealViewControlleryou can pass the value as soon as it is set:
class MyRevealViewController : SWRevealViewController {
// Let's assume this is the outlet to your final VC:
IBOutlet let communityViewController: CommunityViewController!
var community: YourCommunityType {
didSet {
if let communityVC = self.communityViewController {
communityVC.community = self.community
}
}
}
}

How to pass value from navigationController to TabBarController

I have a Login project. The first view controller is NavController and I need to pass user data to tabBarController where I have 3 NavigationControllers.
I tried this.
let openNewVC = self.storyboard?.instantiateViewControllerWithIdentifier("mainNavID") as! UITabBarController
//openNewVC.token = token!
self.navigationController?.pushViewController(openNewVC, animated: true)
You can pass the data from your first view Controller to view controllers which are embedded like as below in UITabbarController.
UITabBarController -> UINavigationController -> UIViewController
You need to traverse the viewControllers instance of UITabBarController and UINavigationController in order to get the instance of UIViewController. Once you will get the instance of 'UIViewController' which is embedded into UITabbarController, you can assign the data as required.
For Example
if let tabBarController = self.storyboard?.instantiateViewControllerWithIdentifier("mainNavID") as? UITabBarController {
// Now you need to get View Controllers array from tabBarControllers which is UINavigationControllers
if let navigationControllers = tabBarController.viewControllers as? [UINavigationController] {
for navigationController in navigationControllers {
//Now you need to get the viewControllers from navigationController stack,
let viewControllers = navigationController.viewControllers
//Now you can assing desired value in viewControllers, I am assuming you need to assign the same value in all viewControler
for viewController in viewControllers {
}
}
}
}
The best way to pass the data in this kind of architecture using Singleton, Assume you created a class Session which member variable token.
class Session {
//Singleton
static let sharedInstance = Session()
//Token which you assign and you can use through out application
var token : String? = nil
}
Now, you can assign the token while pushing the UITabbarController.
let openNewVC = self.storyboard?.instantiateViewControllerWithIdentifier("mainNavID") as! UITabBarController
Session.sharedInstance.token = token
//openNewVC.token = token!
self.navigationController?.pushViewController(openNewVC, animated: true)
Same token you can use within UIViewController
override func viewDidLoad() {
super.viewDidLoad()
if let token = Session.sharedInstance.token {
//Now you have assigned token
}
}
You could try to set the values to the openNewVC as you would set a normal property.
Example:
//set instance
var openNewVC = self.storyboard?.instantiateViewControllerWithIdentifier("mainNavID") as! UITabBarController
// set properties
openNewVC.myFancyString = "Hello world!"
// set view controller active
self.navigationController?.pushViewController(openNewVC, animated: true)