This question already has an answer here:
passing data between viewControllers under a tabController in swift, event sequence
(1 answer)
Closed last month.
I'm trying to pass data to other view on tabBar without performing a transition. But the variable I declared (named distance) turns nil even though I changed Its value.
First VC
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcToSend = storyboard.instantiateViewController(withIdentifier: "listView") as! ListViewController
vcToSend.distance = "abc"
ListViewController
#IBOutlet weak var label: UILabel!
var distance:String?
override func viewDidLoad() {
super.viewDidLoad()
label.text = distance
}
When I tried to print the value of distance, it prints "nil". I checked the names of Storyboard Id and VC. Why I can't pass the data ?
Edit : I tried same code at another View outside the tabBar and it worked, but I still couldn't figured out to do with a tabBar view.
According to Apple Human interface guidelines, you should probably not be doing that.
In any case you would keep this behaviour, the tab bar component is responsible of the instantiation of the new view controller.
What you are doing is instantiating another, which is never shown.
What you can do is something similar to:
self.tabBarController?.viewControllers?.first(where: { $0 is ListViewController })?.distance = 18
Related
I'm building an app (in XCode 8.2.1) where some objects are displayed on a 2D board, and when the user taps one of these objects some info should be displayed about it as a styled modal info box. My design is to have the info written in a separate view controller, which I would display when needed.
I've designed a basic stub for the second view controller and added a single label to it in the interface builder. Then I've ctrl-linked this label to my custom VC class:
class InfoViewController: UIViewController {
#IBOutlet weak var info: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
func displayInfo() {
info.attributedText = NSAttributedString(string: "abc")
}
}
However, when I test my app and tap the object, the info field is nil even in the viewDidLoad() method of my custom VC class. The way I'm displaying my VC is as follows:
let infoViewController = InfoViewController()
infoViewController.modalPresentationStyle = .overCurrentContext
self.present(infoViewController, animated: true, completion: nil)
infoViewController.displayInfo()
(Note: In the end I will have only one single instance of InfoViewController but this is just for testing. I don't expect having a global instance would make any difference?)
As I said, be it inside the viewDidLoad() method or in the displayInfo() method, info is always nil, such that setting its attributedString attribute crashes the app. Thinking the present method might be called asynchronously, I've tried calling displayInfo() from inside viewDidLoad(), but that didn't make any difference.
Can anyone tell my what I've forgotten that would allow my IBOutlet from being properly initialized properly?
Thanks!
David
The problem is the reference to InfoViewController(), which instantiates the view controller independent of any storyboard scene. You want to use instantiateViewController:
let infoViewController = storyboard?.instantiateViewController(withIdentifier: "Info") as! InfoViewController
infoViewController.modalPresentationStyle = .overCurrentContext
present(infoViewController, animated: true) {
infoViewController.displayInfo()
}
A couple of notes:
This assumes that (a) you've given the scene in the storyboard a "storyboard id"; (b) you've set the base class for that scene to InfoViewController.
Note, I called displayInfo in the completion handler of present because you probably don't want that called until the scene has been presented and the outlets have been hooked up.
Alternatively, you can update non-outlet properties of the InfoViewController immediately after instantiating it and then have its viewDidLoad take those properties and update the outlets, e.g.:
class InfoViewController: UIViewController {
var info: String!
#IBOutlet weak var infoLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
infoLabel.attributedText = NSAttributedString(string: info)
}
}
Note, I changed the #IBOutlet name to be infoLabel and added the String property called info. That tends to be the convention, that outlets bear some suffix indicating the type of control, and model objects, like the String property, are without the suffix. (You'll just want to make sure you remove that old outlet in the connections inspector in IB so that you don't have problems with these property name changes.)
Anyway, you can then do:
let infoViewController = storyboard?.instantiateViewController(withIdentifier: "Info") as! InfoViewController
infoViewController.info = "abc"
infoViewController.modalPresentationStyle = .overCurrentContext
present(infoViewController, animated: true, completion: nil)
The key point is don't try to update outlets of the scene immediately after instantiating it, but make sure that this is deferred until after viewDidLoad was called.
I Replaced
let vc = CCDetailViewController()
With
let vc = self.storyboard?.instantiateViewController(withIdentifier: "CCDetailViewController")
Finally
self.present(vc!, animated: true, completion: nil)
Now It Works...
In my case, I had created new view controller for the same class. Which had ended up with two view controllers in storyboard, but referring to the same class. After deleting old view controller, everything worked fine.
Hope it helps to someone.
In my FirstViewController I have two buttons called (button1 and button2).
In my SecondViewController I have two views called(visible1 and visible2).
When I push button1, I will switch to SecondViewController and both
Views should be visible.
When I push button2, only the View(visible2) should be visible.
I tried this:
if (segue.identifier == "segueTest2") {
let svc = segue.destinationViewController as! SecondViewController
svc.visible1.hidden = true
}
but then i will get an error:
When prepareForSegue is called, destinationViewController.view is not loaded yet (and outlets are not connected as well). So visible1 is nil at this point.
IMO best option would be to create some variable mode on second view controller that can take values from enum .AllVisible / .OnlySecondVisible. The in prepareForSegue you set svc.mode = .OnlySecondVisible. And in SecondViewController.viewDidLoad you configure your UI according to selected mode. So first view controller not editing second view controller UI directly. Weak coupling is good.
Another (easier) workaround is to write:
if (segue.identifier == "segueTest2") {
let svc = segue.destinationViewController as! SecondViewControlle
let _ = svc.view // trigger viewDidLoad
svc.visible1.hidden = true
}
But it's poor code design...
So I started working on a basic app and ran into an issue. The user is taken to a first view controller where they are asked to enter two team names and then press the start button (which performs a segue to the second view controller). On the second view controller are two labels (named homeIdentifier and awayIdentifier ) and I want the labels on the second view controller to update to the names the user put in on the first. I thought it would be simple, but have run into an issue; it says "fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)" It also says in red "Thread 1 EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP subcode=0x0)"
How do I do this? The start button is set to perform a segue to the second view controller already, so I don't know if something is going wrong with that process. I've tried the suggestions I've seen here but doesn't seem to work (I don't know if it's because I'm using swift 2, or because I'm transporting 2 variables, etc).
I don't think transporting two variables is the problem. Just make sure that you only perform the segue of there is two variables and one of them does not equal nil. Make sure to also create two variables in the second view controller as you will need to transfer the data over from your first view controller into those variables in the second view controller. To pass data in swift 2 I would use the prepare for segue method. You will also have to set and ID in the attributed inspector for the segue that takes you from the first view controller to the second view controller.
this goes in the first view controller .swift file
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "segueIdentifier") {
// make an instance of the second view controller
var detailVC = segue!.destinationViewController as DetailViewController;
detailVC.variable1 = "\(firstViewControllerVariable)"
detailVC.variable2 = "\(secondViewControllerVariable)"
}
}
this is what your second view controller .swift file should consist of
also make sure all your outlets are linked correctly
import UIKit
class secondViewController: UIViewController {
var variable1: String = String()
var variable2: String = String()
override func viewDidLoad() {
super.viewDidLoad()
homeIdentifier.text = variable1
awayIdentifier.text = variable2
}
}
hope this helps!
FirstVC
let sc=self.storyboard?.instantiateViewControllerWithIdentifier("SecondVC")as! SecondVC
sc.str1="Pass Text"
self.navigationController?.pushViewController(sc, animated:true)
SecondVC
class ViewDetails: UIViewController
{
var str1:String!
override func viewDidLoad()
{
super.viewDidLoad()
print(str1)
// Do any additional setup after loading the view.
}
}
I am really struggling with what should be a simple bit of code.
I have an ios app that has 4 tabs in the uitabcontroller, depending on the a setting in another tab I wanted to replace the controller the first tab goes to. I found that I couldnt simply replace this first tab (although i managed it somehow for a couple of builds then it stopped working after a clean).
So I went for the option of replacing the viewcontrollers that the tab controller references with the .setViewControllers method. I call this from my viewcontroller after the viewdidload method.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc0 = storyboard.instantiateViewControllerWithIdentifier("collection")
let vc4 = storyboard.instantiateViewControllerWithIdentifier("profilenews1")
let vc1: UIViewController! = storyboard.instantiateViewControllerWithIdentifier("news") as UIViewController
let vc2: UIViewController! = storyboard.instantiateViewControllerWithIdentifier("create") as UIViewController
let vc3: UIViewController! = storyboard.instantiateViewControllerWithIdentifier("search") as UIViewController
let controllers = [vc0,vc4]
self.tabBarController?.setViewControllers(controllers, animated: true)
From the research I have done this should work, setViewControllers is documented and I have seen numerous objective c examples but I get EXEC_BAD_INSTRUCTION thrown.
I have checked that the tabcontroller is correct before I replace by doing
println("number of navs: \(self.tabBarController?.viewControllers?.count)")
And that prints the correct number of controllers.
Any ideas?
I need to update a variable in a TableViewController and I can't find the way to do it, can someone explain me why this is not working please?
I'm getting mad.
From my view controller this is the code I'm running:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let TV = storyboard.instantiateViewControllerWithIdentifier("tbController") as myTableViewController
TV.x = "test"
And then, from the TableViewController class:
class myTableViewController: UITableViewController {
var x:String!
override func viewDidLoad() {
super.viewDidLoad()
println("Value of x is: \(self.x)")
}
}
And the printed value is: nil
Why? What is wrong with that? I don't understand :-(
Updated Picture
First, give the segue between the ViewController and the TableViewController an identifier (Example: "TableViewSegue"
Then in the ViewController, use prepareForSegue to pass data from ViewController to TableViewController
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
if segue.identifier == "TableViewSegue" {
let vc = segue.destinationViewController as myTableViewController
vc.x = "Test"
}
}
There could be several issues here.
1. You should embed the tableview into the initial viewcontroller and create it as an IBOutlet
Edit: from the updated picture it appears that you want to click the top right button and go to the tableview. Therefore, this is an incorrect statement.
You also need to make your Viewcontroller (either the tableviewcontroller or the main viewcontroller if you chose to follow #1 above) a UITableViewDelegate and UITableViewDataSource
If you are expecting to change the label listed on the image shown, you will need to use label.text = "String" assignment to change what is displayed there
You have not set an initial variable for x inside the tableviewcontroller.
Also, as a point, your order of operations isn't properly set, so it will always display nil. Because if you look at how you built this:
You have a println inside of a viewdidload on the tableview. This variable you are printing has NOT been set yet, so it is nil
You then created an instance of this class. As soon as you created that instance, the viewdidload method fired and it printed a nil line.
THEN you changed the variable via the TV.x method. But there is no println check there so you're not able to see what you did.