Swift - How to pass UInavigationController and also pass variables - swift

What is the best way to pass a UInavigationController and also pass variables to a new viewController. I know how to do one or the other but not both at the same time. Thank you in advance
this is my current code
func(){
let vc = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! UINavigationController
let posts = self.postList[indexPath.row]
//this is the var that i want to past
//vc.previousViewMessageId = posts.postKey
self.presentViewController(vc, animated: true, completion: nil)
}

If I understand you correctly, you have a view controller that can present a second VC. And this VC is embedded in a UINavigationController. What you don't know how to do, is to pass data from the first VC, to the navigation controller, then to the second VC.
Here is a brute force solution. It's not beautiful, but it works anyway.
Make your own UINavigationController subclass:
class DataPasserController: UINavigationController {
var previousViewMessageId: SomeType?
override func viewDidLoad() {
if let vc = self.topViewController as? YourSecondViewController {
vc.previousViewMessageId = self.previousViewMessageId
}
}
}
Now you can add a navigation controller in the storyboard, set its class to DataPasserController, and connect the second VC to it as its root view controller.
Now suppose you have got an instance of DataPasserController by calling instantiateViewControllerWithIdentifier, you can do this:
yourDataPasserControllerInstance.previousViewMessageId = posts.postKey
And present the instance!

To pass a value to your Navigation Controller's Root View Controller, you access viewControllers[0] and cast it to the class of your Messages View Controller (the controller that has the previousViewMessageId property):
func () {
let messagesNC = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! UINavigationController
let messagesVC = messagesNC.viewControllers.first as! MessagesViewController
messagesVC.previousViewMessageId = postList[indexPath.row].postKey
presentViewController(messagesNC, animated: true, completion: nil)
}

What you have there is simply presenting a view controller... You are skipping the navigation controller.
What you need to do is present the new view controller inside the navigation controller. Once you have done that, it will show correctly. You can also pass the variables after you've created the vc variable.
This presents the new viewController (vc) within the navigation controller...
self.navigationController?.pushViewController(vc, animated: false)
This sets the variable in the new viewController (vc) (you are correct)
vc.previousViewMessageId = posts.postKey
So complete:
func(){
let vc = storyboard?.instantiateViewControllerWithIdentifier("messagesViewController") as! MessagesViewController
let posts = self.postList[indexPath.row]
//this is the var that i want to past
vc.previousViewMessageId = posts.postKey
navigationController?.pushViewController(vc, animated: false)
}
PS. While not part of the question, I feel I should still mention... Use of the word self should be left to necessity only. In other words, don't use it when it isn't needed. for example self.postList[indexPath.row] :)
https://github.com/raywenderlich/swift-style-guide#use-of-self

Related

How to navigate between tabs "TabBarController" and transfer data between them?

this code works almost as it should, but it opens a second window on top of the main one, and not separately. How do I programmatically navigate to another view controller?
let tableVC = self.storyboard?.instantiateViewController(withIdentifier: "SaveTableViewController") as! SaveTableViewController
tableVC.titleText = alert.textFields![0].text ?? "Test"
tableVC.tabBarController?.selectedIndex = 1
//self.tabBarController?.showDetailViewController(tableVC, sender: self)
//self.present(tableVC, animated: true, completion: nil)
Your tabBarController have a viewControllers array. If you want to show a specific ViewController programmatically you could set the selectedViewController attribute of your tabBarController to an element out of the viewControllers array...
For more detail: https://developer.apple.com/documentation/uikit/uitabbarcontroller/1621172-selectedviewcontroller

How to invoke a method from a modal view controller class in Swift?

Basically for this simple game app I have 2 different UIViewControllers called ViewController and PreviewController. PreviewController is opening view with the title screen and a label titled "Start game". When the label is tapped, it initiates a modal view controller (the ViewController class that has all the views for the actual game itself) and calls the "EnterNewGame" method from ViewController that sets up the game. Right now the issue I have is when calling this method, only part of the method seems to be running.
Here is the function in PreviewController that is being initiated upon tap:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
self.present(ViewController(), animated: true, completion: {() -> Void in
ViewController().enterNewGame()
})
}
And here is the EnterNewGame() method from ViewController
func enterNewGame() {
//show suit indicators when starting a new game
bluePlayerSuitsHidden = false
redPlayerSuitsHidden = false
game.blueTurn = true
self.setBackground()
self.cleanUpBoard()
self.createBoard()
self.displayBoard()
self.setSuitIndicators()
self.highlightCards()
playButton.isEnabled = false
}
Right now, when the label is tapped the screen transitions to the modal view controller but only displays a black screen with only one of the game setups (setting a few images on the top of the screen) working properly. I am sure that the EnterNewGame method works properly to actually start the game because I have tested it in isolation, so I think I am just not setting up the modal view controller properly or I have to call the method differently. Any help is appreciated, thanks.
Controller on which you're calling your method ins't the same instance as controller which you're presenting, you need constant (also your code can be simplified by avoiding using self references and writing name of completion parameter with specifing closure's parameter and return type)
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
let controller = ViewController()
present(controller, animated: true) {
controller.enterNewGame()
}
}
Also, you can call this method on some other method inside your certain controller like viewDidLoad, viewWillAppear or you can create factory method which would return you certain set controller.
This last part leads me to idea: look how you instantiate your controller and look carefully if you don't need to instantiate it through storyboard or nib file.
class ViewController: UIViewController {
class func instantiate() -> ViewController {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Identifier") as! ViewController
// let controller = ViewController(nibName: "ViewController", bundle: nil)
controller.enterNewGame()
return controller
}
}
Usage:
#objc func handleButtonTap(_ recognizer: UITapGestureRecognizer) {
present(ViewController.instantiate(), animated: true)
}

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

How to instantiate a view controller programatically, without storyboard

Is there a way to instantiate a view controller programatically without using storyboard identifier. I want to push CNContactPickerController into my root view controller .
let controller = CNContactPickerViewController()
controller.delegate = self
navigationController?.present(controller, animated: true, completion: nil)
i did this but in contactPicker (delegate) i push a VC from storyboard where i save what info i want from that specific contact.
The problem: when i pop the last view controller i want to go on the CNConctactPickerControllerView but i go on the first view controller
i tried with dismiss but nothing happens..
The problem with doing this is that NONE of the UI for your view controller is created. What you are doing is simply instantiating an instance of the class. None of the corresponding UI is created, also your IBOutlets will be nil.
You have 2 options, either you use Interface builder and instantiate from the Nib or Storyboard, or you create ALL your UI manually in code.
For the sake of resuability you could create a static method on CNContactPickerViewController to handle the storyboard instantiation for you. See the following:
class CBContactPickerViewController: UIViewController {
static func fromStoryboard() -> CNContactPickerViewController {
return UIStoryboard(name: "foobar", bundle: nil).instantiateViewController(identifier: "CNContactPickerViewController") as! CNContactPickerViewController
}
}
You can then utilise this as follows:
self.present(viewController: CNContactPickerViewController.fromStoryboard(), animated: true, completion: nil)

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)