i am trying to build my first iOS app with swift. I am stuck at a segue where i want to give the row indexpath to the next page, but the indexpath is "optional(0)" instead of "0". does anyone know why? in the code, there's a print sender. That one says "optional(0)" The variable "viewController.passedValue" is set to 0, the real code is behind it in comments
does anyone know why it gives "Optional(0)"?
Code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let viewController = segue.destinationViewController as! NewsDetailViewController
print(String(sender))
viewController.passedValue = 0 // sender as! int
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("DetailSegue", sender: indexPath.row)
print("HIER")
}
You are calling
performSegueWithIdentifier("DetailSegue", sender: indexPath.row)
And then, iOS is calling your prepareForSegue, in which sender is defined as an AnyObject?, i.e. an optional AnyObject:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
...
}
Thus, when you print sender, it's an optional and thus informs you of such. If you don't want the optional, you could unwrap it (either forced unwrapping or, better, optional binding).
--
As an aside, though, it is a misuse of sender to be storing the indexPath.row there. As the documentation for performSegueWithIdentifier says, the sender is:
The object that you want to use to initiate the segue. This object is made available for informational purposes during the actual segue.
But indexPath.row is not the object that initiated the segue (the table view cell is). I therefore would advise against using the sender to hold the indexPath.row. A more common pattern would be
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destination = segue.destinationViewController as? NewsDetailViewController, let row = tableView.indexPathForSelectedRow?.row {
destination.passedValue = row
}
}
This also has the virtue of giving you the alternative of not defining a didSelectRowAtIndexPath at all, and just represent your segue from the storyboard's cell prototype directly to the NewsDetailViewController scene. If you really want to segue programmatically, you can do that, but it's not necessary. But I would not advise using sender to represent the indexPath.row, but rather the UI control that triggered the segue.
Related
I tried to pass the value of the cell that the user clicks on to another view controller.
Here is my code.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
productDisplayed = currentProductList[indexPath.row] as? Product
performSegue(withIdentifier: "ProductDetailSegue", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if ( segue.identifier == "AddProductSegue"){
let controller: AddProductViewController = segue.destination as! AddProductViewController
controller.delegate = self
}
if ( segue.identifier == "ProductDetailSegue"){
let controller: ProductDetailsViewController = segue.destination as! ProductDetailsViewController
controller.currentProduct = productDisplayed
}
}
Firstly, I didn’t write “performSegue()”.
The issue I met is that the screen will be transferred firstly rather than assign the value to “productDisplayed”, which means the object “currentProduct” in the second VC is nil.
Then I added “performSegue()”.
It still didn’t work. The thing is the second screen was displayed twice. The first time is same with the picture above. And the second time is correct.
But when I tried to click the back button on the left top. It returned to the nil page rather than the ProductDetail page. The screenshot is as follows.
It seems like "prepare" method always being called first then the tableView. How to change the order? If can, this should be fixed I think.
Hope to get your help.
Thank you.
Cause of Problem:
In your storyboard, you may have bound, ProductDetailsViewController with UITableViewCell directly.
It means, upon click/selection of row, it will perform segue (navigation) operation directly.
At the same time, programatically you perform navigation operation using performSegue(withIdentifier: "ProductDetailSegue", sender: self)
Solution:
You have two ways to solve this issue.
I recommend - In your story board, switch segue connection from UITableViewCell to UIViewController (Remove segue connection from UITableViewCell and attach the same segue connection with UIViewController of your tableview)
In your source code, delete/remove this line performSegue(withIdentifier: "ProductDetailSegue", sender: self) and handle data transmission from this function override func prepare(for segue: UIStoryboardSegue, sender: Any?)
EDIT based on provided answer:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedName = quizNames[indexPath.row]
performSegueWithIdentifier("saveSegue", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "saveSegue" {
let inputController = segue.destinationViewController as? QAInputViewController
inputController?.quizName = selectedName
}
}
I have done some research on here trying to figure out exactly how to use prepareForSegue in order to pass the selected Core Data object from a UITableView cell to a second View Controller, but what i found and tried to implement did not work out for me as it seems some of the code might be outdated. Here is what i am trying to do:
I populate a UITableView with an array of names of type Name which is my NSManagedObject. When i select a specific name it will segue me to an input screen where a question and answer will be submitted. I want to make sure that the question and answer that are submitted stay assigned to the previously selected name. I am not sure how to set this up in prepareForSegue. I did set up my data model with relationships, so if i did that correctly it should work once i understand how to implement it. Is there some way i can check the currently selected name so that i know any question and answer that is inputted will be saved to that name?
Relative to my above question, i want the title of the second View Controller to show the name of the previously selected name.
Basically i am just looking for the guidance on to get this implemented in Swift 2. Thank you very much for your help.
EDIT: Passing Core Data objects from UITableViewCell to another View Controller
The above link is the solution i tried implementing that was not working for me. Specifically:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showTaskDetail" {
let detailVC: TaskDetailViewController = segue.destinationViewController as! TaskDetailViewController
let indexPath = self.tableView.indexPathForSelectedRow()
let thisTask = fetchedResultsController.objectAtIndexPath(indexPath!) as! TaskModel
detailVC.detailTaskModel = thisTask
detailVC.delegate = self
}
This is how I do it:
At the top of my class I'll have a var that stores the selected item:
var selectedTask: Task?
Then in my tableViews didSelectRow method:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedTask = tasks[indexPath.row]
performSegueWithIdentifier("showTaskDetail", sender: self)
}
And finally:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showTaskDetail" {
let detailVC: TaskDetailViewController = segue.destinationViewController as! TaskDetailViewController
detailVC.detailTaskModel = selectedTask
detailVC.delegate = self
}
}
However, I'd be more inclined to just pass some kind of ID/name and perform another fetch with a new moc rather than pass around core data objects.
You'll get less threading issues this way and it will make things far easier should you need to deep link into your app, from say, a notification or something.
Mocs are cheap, use them once, and throw them away.
I have a tableView with 7 rows, each row is a different action. When a user taps on a row, a view controller is shown as a popover and a text field is displayed for them to add a note. I then want to save this note to Parse but I need to save the passedChildID with it and therefore it (passedChildID) needs to be passed from the tableviewcontroller. I have this working in other area's of my app from one tableView to another tableView but for some reason it won't work with the popover.
The following code is in SingleChildViewTableViewController.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "childToNote" {
let popoverViewController = segue.destinationViewController
popoverViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
popoverViewController.popoverPresentationController!.delegate = self
let noteActionViewController = segue.destinationViewController as! actionNoteViewController
if let indexPath = tableView.indexPathForCell(sender as! UITableViewCell) {
noteActionViewController.passedChildID = passedChildID
}
}
if segue.identifier == "childToItemNeeded" {
//other stuff
}
}
This builds and runs fine, but when I tap on that particular row, I get the following error.
Could not cast value of type '<<app_name>>.SingleChildTableViewController' (0x10e1bef60) to 'UITableViewCell' (0x110bca128).
I have tried moving the let noteActionViewController... code above the let popoverViewController figuring that the former was never getting run but that didn't change anything.
The segue is being performed as followed (in case it makes any difference).
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 0 {
performSegueWithIdentifier("childToNote", sender: self)
}
}
I'm stumped because this code has worked elsewhere in my app without fail.
it looks like your problem lies in this line
indexPathForCell(sender as! UITableViewCell)
the sender in this case you are telling it to be self, which is a UIViewController
performSegueWithIdentifier("childToNote", sender: self)
You are then telling in the indexPathForCell line to cast it as a UITableViewCell but its of type SingleChildTableViewController. So change the sender to be the UITableViewCell
Maybe you can save the indexPath as a local variable and when you select a cell you change the value of the variable.
I know how to pass data using segues from prepareForSegue function, but the thing i have a TableViewCell from which there are two possible segues to two different ViewControllers (let's just say A, B as of now).
It was suggested here that it is best to connect the segues to View controller rather than the tableCell itself, which infact worked perfectly. But i want to pass information to the second View controller when the cell is clicked, So how to access segue that i connected to the Source ViewController.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showQuestionnaire"{
if let indexPath = self.tableView.indexPathForSelectedRow() {
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! QuestionnaireController
let patientQuestionnaire = patientQuestionnaires[indexPath.row] as! PatientQuestionnaire
controller.selectedQuestionnaire = patientQuestionnaire
self.performSegueWithIdentifier("showQuestionnaire", sender: self)
}
}
}
Again: This question is not regarding passing information through prepareForSegue
You should be using the didSelectRowAtIndexPath method to determine whether or not a cell was selected, and send the indexPath as the sender of the segue:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("showQuestionnaire", sender: indexPath);
}
Then in your prepareForSegue method, get the indexPath from the sender parameter and use that to pass the correct row/data:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "showQuestionnaire") {
let controller = (segue.destinationViewController as! UINavigationController).topViewController as! QuestionnaireController
let row = (sender as! NSIndexPath).row; //we know that sender is an NSIndexPath here.
let patientQuestionnaire = patientQuestionnaires[row] as! PatientQuestionnaire
controller.selectedQuestionnaire = patientQuestionnaire
}
}
To explain...
I used the index path as the sender so I can easily pass the index path. You could also check for the currently selected cell using other UITableView methods, but I've always done it this way with success
You can't put performSegueWithIdentifier within the prepare for segue method, because performSegueWithIdentifier leads to prepareForSegue; You are just looping around and around with no aim. (When you want to perform a segue, prepareForSegue is always executed)
prepareForSegue doesn't run by itself when a row is selected. This is where you need didSelectRowAtIndexPath. You need a performSegueWithIdentifier outside of the method as described previously, which should be in didSelectRowAtIndexPath
I was following this tutorial http://www.raywenderlich.com/76519/add-table-view-search-swift when I ran into an error. I am adding this feature into an app I was already working on. Once I am in the booths table view, I want to be able to navigate out into the main menu with a button on the navigation bar. Here is the section of code that deals with the segues.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("BoothDetail", sender: tableView)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "BoothDetail" {
let BoothDetailViewController = segue.destinationViewController as UIViewController
if sender as UITableView == self.searchDisplayController!.searchResultsTableView {
let indexPath = self.searchDisplayController!.searchResultsTableView.indexPathForSelectedRow()!
let destinationTitle = self.filteredBooths[indexPath.row].name
BoothDetailViewController.title = destinationTitle
} else {
let indexPath = self.tableView.indexPathForSelectedRow()!
let destinationTitle = self.booths[indexPath.row].name
BoothDetailViewController.title = destinationTitle
}
}
}
}
The error is thrown while trying to use the back button on the booths list that is a direct show segue to the main conference menu. The error is on this line.
if sender as UITableView == self.searchDisplayController!.searchResultsTableView {
You have quite a few problems. Some fatal, some just a headache.
the first headache is you are calling the same segue twice. Both functions call the same segue. Both will execute. Now if you want a double animation, okay. But since one passes data and the other does not, you may have an issue. Eliminate the didSelectRowAtIndexPath function.
In your prepareForSegue method it appears you have two different objects connected to the same segue. A searchDisplayController and a tableView. You want two separate segues. Then your if/else makes changes based on which segue was chosen:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segue1" {
//code set 1
} else if segue.identifier == "segue2" {
//code set 2
}
}
I had similar problem and I just got it solved. When creating your segue in the tableview do not drag it from the cell, create a manual segue called "BoothDetail"
and connect it to BoothDetailViewController, to create a manual segue select the table view controller and click on "show connection inspector" you will see manual under triggered segue .
My problem the sender was a tableviewcell and not the tableview, so the function
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("BoothDetail", sender: tableView)
}
was never called to pass the tableview controller so when you try to cast it you were getting the error.
good luck