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.
Related
I am developing an iOS app using Swift and I have a view controller that segues from a table view cell content view by selecting a cell row or selecting a button inside of that cell row's content view. The original view controller that contains the table view performs a segue on two different occasions: one segue when the cell row itself is selected (segues to an avplayerviewcontroller and plays a video depending on the cell row that was selected) and the second segue happens when you press a button that is inside of the content view of the table view cell. In the first segue, I am able to pass the the cell row that is selected with if let indexPath = self.tableview.indexPathForSelectedRow when I override the first segue. However when I try to pass the cell row that was selected when I try to override the second segue that happens when you press the button it doesn't work. Is this because the button inside of the table view cell doesn't know which row it was selected in? If so, how can I solve this problem, and if not what is a viable solution to solve such issue? Reminder: The "playDrill" segue is trigged when you select a cell row, the "Tips" segue is trigged when you selected a button inside of that same cell row's content view
Code for first segue that happens when you select a cell row (this segue functions as desired):
class DrillsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "playDrill" {
if let indexPath = self.tableView.indexPathForSelectedRow {
if initialRow == 1 {
drillVid = videoURL[indexPath.row]
playerViewController = segue.destination as! PlayerController
playerViewController.player = AVPlayer(playerItem: AVPlayerItem(url: drillVid))
playerViewController.player?.play()
print(indexPath) //prints correct value which is "[0,6]"
}
if initialRow == 3 {
drillVid = videoUrl[indexPath.row]
playerViewController = segue.destination as! PlayerController
playerViewController.player = AVPlayer(playerItem: AVPlayerItem(url: drillVid))
playerViewController.player?.play()
}
Code for second segue that triggers when you select a button inside of the cell's content view (I want this segue to have the value of indexPath as in the first segue, but when I try to use that code it doesn't return the correct value):
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Tips" {
if let indexPath = self.tableview.indexPathForSelectedRow {
if initialRow == 1 {
print(indexPath) //the value printed is "6", I want "[0,6]" like the first code block
let tipVC = segue.destination as! KeysController
}
}
}
}
I had this issue also a couple of months ago... finally I was able to solve it with the following tasks:
Create an Outlet and Action of the button in your corresponding TableViewCell
Create a XYTableViewCellDelegate protocol in your TableViewCell.swift file
Define a delegate of your previous created TableViewCell delegate in your TableViewCell class
Define your delegate in cellForRowAt: function of your tableview
Add the delegate to your ViewController class where the TableView is also implemented
Finally just create this function in your ViewController to receive the tapped buttons tableview indexpath
If you need more help on this, please just copy / paste your whole ViewController & TableViewCell class here - We can make then the changes directly in the code.
It should look like the following code:
// FILE VIEWCONTORLLER
// -> 5.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, XYTableViewCellDelegate {
// ... view did load stuff here
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TripDetailsTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
// -> 4.
cell.delegate = self
// ...
}
// -> 6.
func buttonTappedViewCellResponse(cell: UITableViewCell) {
let indexPath = tableView.indexPath(for: cell)
// do further stuff here
}
}
// FILE TABLEVIEWCELL
// -> 2.
protocol XYTableViewCellDelegate {
func buttonTappedViewCellResponse(cell:UITableViewCell)
}
class XYTableViewCell: UITableViewCell {
// -> 1.
#IBOutlet weak var button: UIButton!
// -> 3.
var delegate: XYTableViewCellDelegate?
// -> 1.
#IBAction func buttonAction(_ sender: Any) {
self.delegate?.buttonTappedViewCellResponse(cell: self)
}
}
You are not able to get the selected index of the selected cell because you are not actually selecting a cell. You are pressing a button inside the cell.
So, what you do is get a reference to the button, get the button's superview (the cell) and then you can get the indexPath of that cell.
class TableViewController: UITableViewController {
var indexPathForButton: IndexPath?
#IBAction func buttonPressed(_ sender: UIButton) {
let button = sender
let cell = button.superview!.superview! as! MyCell // The number of levels deep for superviews depends on whether the button is directly inside the cell or in a view in the cell
indexPathForButton = tableView.indexPath(for: cell)
}
Then in prepare(for segue:)
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Tips" {
if initialRow == 1 {
print(indexPathForButton)
let tipVC = segue.destination as! KeysController
}
}
}
I have never used tableview.indexPathForSelectedRow (and while I think it is not good practice, I am not sure if it is responsible for your issue), however one other approach you might try is to send the indexPath object as sender. This would avoid having to check for tableview.indexPathForSelectedRow inside prepareForSegue. For instance,
You can trigger your "playDrill" segue in tableView(UITableView, didSelectRowAt: IndexPath) where you have access to this indexPath and can simply pass it as sender.
When triggering your "Tips" segue, you can also pass this indexPath object as sender. One way to have a reference is to keep the indexPath object when you dequeue your cell and set the button action in ableView(UITableView, willDisplay: UITableViewCell, forRowAt: IndexPath)
Let me know if that helps you! Cheers,
Julien
I need to pass a variable containing the index of the selected row in one view to the next view.
firstview.swift
var selectedRow = 0;
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
selectedRow = indexPath.row + 1
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "viewAssignmentsSegue") {
let navController = segue.destinationViewController as! UINavigationController
let controller = navController.viewControllers[0] as! AssignmentsViewController
print(selectedRow)
controller.activeCourse = selectedRow
}
}
My issue is that, when a row is selected, the selectedRow variable isn't updated by the tableView method before the segue occurs, meaning that the value is essentially always one behind what it should be. How can I delay the prepareForSegue until the variable is updated or how else can I successfully pass the selected row to the next view without delay?
One possibility: Don't implement didSelectRowAtIndexPath:. Just move that functionality into your prepareForSegue implementation. That, after all, is what is called first in response to your tapping the cell. Even in prepareForSegue you can ask the table view what row is selected.
Another possibility: Implement willSelectRowAtIndexPath: instead of didSelectRowAtIndexPath:. It happens earlier.
in your prepareForSegue:
controller.activeCourse = self.tableView.indexPathForSelectedRow
don't wait for didSelect to be called - react earlier.
Use this on prepareForSegue:
let path = self.tableView.indexPathForSelectedRow()!
controller.activeCourse = path.row
or
let path = self.tableView.indexPathForCell(sender)
controller.activeCourse = path.row
I have two table view controllers
InvoiceList view controller
InvoiceShow view controller
I use didSelectRowAtIndexPath method as bellow to get selected table cell specific value
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let rowObject = objects[indexPath.row]
let invoicehash = rowObject["hash_key"]!
}
i need to send invoicehash value to InvoiceShow controller when click the table cell of InvoiceList
i tried to use prepareForSegue function. but it is not applicable because it will trigger before the didSelectRawAtIndexPath function. so when i implemented it, gives the previous click event variable value. not correct one.
Please help me to access invoiceHash variable value from InvoiceShow controller
You will get the selected cell in prepareForSegue method itself.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let selectedIndexPath = self.tableView.indexPathForSelectedRow()!
let rowObject = objects[selectedIndexPath.row]
let invoiceHash = rowObject["hash_key"]!
let invoiceShowViewController = segue.destinationViewController as! InvoiceShowViewController
// Set invoiceHash to `InvoiceShowViewController ` here
invoiceShowViewController.invoiceHash = invoiceHash
}
You can still use a segue if you want and/or already setup on your storyboard.
You just need to connect the two view controllers in Interface Builder directly from one to another.
So, start ctrl-dragging from the controller itself and not from the TableViewCell (take a look at the screenshot)
then use the performSegueMethod with the new segue identifier like this:
self.performSegueWithIdentifier("mySegueIdentifier", sender: self)
and finally, your prepareForSegue method:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "mySegueIdentifier" {
let selectedIndex = self.invoiceTableView.indexPathForSelectedRow
//if element exist
if selectedIndex?.row < myDataSourceArray.count {
let destination = segue.destinationViewController as! InvoiceShowViewController
let invoice = myDataSourceArray[selectedIndex!.row]
destination.invoice = invoice
}
}
}
That's it!
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
So I have a table view that I want to segue to specific views when a specific cell is selected. For example, if the cell at index 0 is selected, I want the "RedViewController" to be visible.
The example I keep receiving looks something like this (located in the VC with the table view)
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showDetail" {
if let destination = segue.destinationViewController as? RedViewController {
if let blogIndex = tableView.indexPathForSelectedRow()?.row {
destination.blogName = swiftBlogs[blogIndex]
}
}
}
}
(Where blogName and swiftBlogs are random examples)
But this just loads specific data into a singular view controller. Preferably I want a switch statement for each index path that makes a specific VC visible.
Ok, so write your code that way. Don't link your table views directly with a segue. Instead write code in your didSelectRowAtIndexPath.
In that method you can write a switch statement, or use an array lookup, to load a view controller using a unique ID and the instantiateViewControllerWithIdentifier, or trigger a through code using performSegueWithIdentifier.
I figured it out thanks to Duncan C and a little more research!
In the table view VC I added the handy function
func switchToViewController(identifier: String) {
let viewController = self.storyboard?.instantiateViewControllerWithIdentifier(identifier) as! UIViewController
self.navigationController?.setViewControllers([viewController], animated: false)
}
And in the didselectRowAtIndexPath
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let selectedRow = indexPath.indexAtPosition(indexPath.row)
switch selectedRow {
case 0:
switchToViewController("RedVCIdentifier")
default:
println("Unknown Cell selected")
}
}
But also had to add a Storyboard ID to the RedViewController in the storyboard as "RedVCIdentifier"