Passing data from table view cell to new view controller error - ios

I'm trying to pass data from my tableViewCell, however, keep getting an error on my let cell = sender as! UITableViewCell
fatal error: unexpectedly found nil while unwrapping an Optional
value.
How would I make my cell an optional? I'm trying to pass the data from an array.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? UsersProfileViewController{
let cell = sender as! UITableViewCell
let selectedRow = partnerTable.indexPath(for: cell)?.row
if let name = usersArray[selectedRow!].firstLastName{
destination.name = name
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "toUser", sender: nil)
}
}

You should trigger this way
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "toUser", sender: indexPath.row)
}
and in prepare for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
let selectedRow = sender as? Int
if let name = usersArray[selectedRow!].firstLastName{
destination.name = name
}
}

If you prefer to use segue using storyboard:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = tableView.indexPathForSelectedRow {
guard let destinationVC = segue.destination as? UsersProfileViewController else {return}
let selectedRow = indexPath.row
destinationVC.name = usersArray[selectedRow].firstLastName
}
}
You can also segue to the destination ViewController programatically and pass the required value to that Controller.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let sb = UIStoryboard.init(name: "StoryboardName", bundle: nil)
let destinationVC = sb.instantiateViewController(
withIdentifier: "StoryboardId for viewcontroller set in storyboard") as? UsersProfileViewController
guard let name = usersArray[selectedRow!].firstLastName { else return }
destinationVC.name = name
self.presentViewController(destinationVC, animated: true, completion: nil)
}
Steps:
Get an instance of the storyboard.
Instantiate the required viewcontroller with the help of storyboard Id which is assigned in the storyboard.
Pass the data required to destination VC.
Present the destination View Controller.

I got one thing from you post, you want to get current selected tableview data and want to pass it to next view controller, you can do this in a simple way. Check tableview cell selected delegate method
didSelectRowAtIndexPath
From this delegate method you can save selected row index and in prepare segue method call this function in this way , selectedRow value you can set in didSelectRowAtIndexPath delegate method.
usersArray[selectedRow!].firstLastName

Steps To Do:
First of all create the property selectedIndex in View controller.
set the selectedIndex property value with indexPath from didSelectRowAtIndexPath table view's delegate methods.
In Prepare for Segue method you can find the data value from your datasource object as you have selected index.
4.Pass the value from datasource for selected index and make selectedIndex property to nil.

Related

Set a string as a var after selecting a UITableView cell in Swift 3

I am making an audio app, and I am populating a table view controller with data from JSON. Based on the user's selection, I want to pass the episode_name, shown in the cell, into the next view after segue.
So far, the table loads with data, I can pass a locally defined variable to the next view, but I can't copy the string from the cell into that variable.
Here's my code.
func extract_json(_ data: Data)
{
//... removed to condense
if let shows_list = json as? NSArray
{
for i in 0 ..< data_list.count
{
if let shows_obj = shows_list[i] as? NSDictionary
{
let episode_name = shows_obj["episode"] as? String
let episode_date = shows_obj["date"] as? String
TableData.append(episode_date! + " | " + episode_name!)
}
}
}
DispatchQueue.main.async(execute: {self.do_table_refresh()})
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "passer", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "passer" {
let vc = segue.destination as! EpisodeViewController
vc.variableInSecondVc = "Pass Variable" // this is where I want to pass episode_name
}
} // end segue
Solutions I have tried:
1) if I call episode_name immediately, it flags it because that variable is contained in the prior function,
2) if I try to run the extract_json function in the ViewDidLoad, it's causing other issues in the code.
I'm new to Swift and unsure -- is there a better way to "copy" the string from that cell and pass it to the vc.variableInSecondVc?
EDIT: One point of clarification. If I ran this code, it would successfully change the subsequent UILabel to "Pass Variable" -- but of course, I actually want to turn PassVariable dynamic by changing it to reflect the string of episode_name.
The performSegue(withIdentifier:sender:) method takes two arguments, 1. The Segue identifier, 2. The parameter you want to pass of type AnyObject?
self.performSegue(withIdentifier: "passer", sender: indexPath)
In the prepare(for:sender:) method, you check the segue identifier and cast the sender parameter to the type you had passed earlier, and get data from the array list to pass it to next view controller.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "passer" {
guard let indexPath = sender as? IndexPath else { return }
let data = TableData[indexPath.row] //write your logic here to get value from your data list based on index path row value and pass value to view controller.
let vc = segue.destination as! EpisodeViewController
vc.variableInSecondVc = data //value which you got from list based on indexPath
}
}
Get data from indexPath
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "passer" {
let vc = segue.destination as! EpisodeViewController
vc.variableInSecondVc = TableData[indexPath.row]
}
}
And carefully, you should get the string in viewDidAppear in secondViewController
override func viewDidAppear() {
label.text = variableInSecondVc
}
Thanks for the help, all.
While these solutions didn't work, they helped me rethink the problem, and I found a solution that will pass the value to the label in the next VC successfully. Here's the approach:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "passer", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = self.tableView.indexPathForSelectedRow {
let controller = segue.destination as! EpisodeViewController
controller.variableInSecondVc = TableData[indexPath.row]
}
}
The label now updates in the next view controller.
try this it will send the selected string to next View controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "passer" , let indexPath = sender as? IndexPath{
let vc = segue.destination as! EpisodeViewController
vc.variableInSecondVc = TableData[indexPath.row]
}
}

Swift/XCode - Why can't I pass data to the next view controller when a tableViewCell is pressed?

Apologies if this has already been answered elsewhere- I have spent the last two hours trying different things using similar suggestions but I still can't solve it!
Basically, I have a UITableView with custom cells(for different quiz topics) that when pressed, should allow the app to go to the next VC and pass an integer so the next VC knows which quiz to load. In my table view VC I have this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
rowPressed = indexPath.row
print ("rowPressed = \(rowPressed) before VC changes")
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is GameVC {
let vc = segue.destination as? GameVC
vc?.toPass = rowPressed
print("I ran...")
} else {
print("You suck")
}
and in my GameVC I have this:
var toPass = Int()
override func viewDidLoad() {
super.viewDidLoad()
print("toPass = \(toPass)")
The console output when run is this:
I ran...
toPass = 0
rowPressed = 3 before VC changes
So it looks like the VC changes before the previous one can send the correct value of toPass. How do I fix this?
Many thanks in advance!
According to the segue description:
For example, if the segue originated from a table view, the sender parameter would identify the table view cell that the user tapped.
So, sender is a UITableViewCell and you can do like below:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard let cell = sender as? UITableViewCell else { return }
guard let idx = tableView.indexPath(for: cell) else { return }
guard let vc = segue.destination as? GameVC else { return }
vc.toPass = idx.row
}
The problem of your code was prepare(segue:) was invoked before tableView(didSelectRowAt:).
As suggested you have to call the performSegue method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// not necessary
//rowPressed = indexPath.row
//print ("rowPressed = \(rowPressed) before VC changes")
performSegueWithIdentifier("Your id", sender: indexPath)
//You can set the identifier in the storyboard, by clicking on the segue
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Your id"{
var vc = segue.destinationViewController as! GameVC
vc.toPass = (sender as! IndexPath).row
}
}
Because prepareForSegue is called before didSelectRowAt, you can also remove the segue from the Storyboard, drag and drop holding ctrl from the UITableViewController's yellow icon in the top to the second ViewController, click on the segue, give it an identifier, so now you can call performSegue:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.pressed = indexPath.row
self.performSegue(withIdentifier: "segue identifier", sender: nil)
}

TableViewCell to ViewController - Swift

I tried many ways but still can't go to another ViewController. This is my storyboard.
and This is my code.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? HistoryMainTableViewCell {
listItems.sort() { $0.seq > $1.seq }
let history = listItems[indexPath.row]
cell.setValue(history: history)
return cell
} else {
return HistoryMainTableViewCell()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "vcProductHistory" {
if let productHistoryPage = segue.destination as? HistoryProductViewController {
if let IndexPath = tableViewHistory.indexPathForSelectedRow {
let historyArray = listItems[IndexPath.row]
productHistoryPage.history = historyArray
}
}
}
}
Try this code:(Untested Code)
Note: Below code will trigger prepare segue. When, tableViewCell selected.Let me know the code works...
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
// let indexPath = myTableView!.indexPathForSelectedRow
// let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
//sendSelectedData = (currentCell.textLabel?.text)! as String as NSString
performSegue(withIdentifier: "vcProductHistory", sender: self)
}
Based on your answer to my initial comment: dragging the line from initial vc to destination vc tells your vc about the segue, preparing for segue tells your vc what do when the segue is triggered, but you still need to let your vc know when it should perform the segue. try this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? HistoryMainTableViewCell else { return HistoryMainTableViewCell() }
let history = listItems[indexPath.row]
cell.setValue(history: history)
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "vcProductHistory" {
guard let productHistoryPage = segue.destination as? HistoryProductViewController, let indexPath = sender as? IndexPath else { return }
let historyArray = listItems[indexPath.row]
productHistoryPage.history = historyArray
}
}
func tableView(_ tableView: UITableView, didSelectRowAtIndexPath indexPath: IndexPath) {
performSegue(withIdentifier: "vcProductHistory", sender: indexPath)
}
I think this is a little bit neater using the guard syntax. Also, it uses sender as the indexPath, not using tableView.indexPathForSelectedRow.
Also, I noticed you have this in your cellForRowAtIndexPath:
listItems.sort() { $0.seq > $1.seq }
Your sorting your data source in cellForRowAtIndexPath. You can't do that there because that function gets called for each row individually. You need to do the sorting before that is called, somewhere like viewWillAppear. Also, to sort an array in place you need to remove the () after sort. try this.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
listItems.sort { $0.0.seq > $0.1.seq }
tableView.reloadData()
}
I think you should connect to UINavigationController , not to HistoryProductionViewController
Because your MainViewController's segue connected to Navigation Controller
I guess in func prepareForSegue segue.identifier == "vcProductHistory" it works but, if let productHistoryPage = segue.destination as? HistoryProductViewController not works
because your destination ViewController's class is NavigationController
you should call
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "vcProductHistory" {
if let naviVC = segue.destination as? UINavigationController {
if let productHistoryPage = naviVC.topViewController as? HistoryProductViewController {
if let IndexPath = tableViewHistory.indexPathForSelectedRow {
let historyArray = listItems[IndexPath.row]
productHistoryPage.history = historyArray
}
}
}
}
}
Your solution - as you described it - should work.
The most work here can be done directly in the Storyboard.
Select the prototype cell in the TableView, drag the Segue to the NavigationController and select presentModally.
This should work without further magic.
When selecting the TableViewCell the NavigationController should be presented. There is also no code needed at this point (except for the population of the TableView).
If not - you maybe misconfigured your TableView by setting the selection to No Selection:
or you set User Interaction Enabled to false somewhere.

Swift 2.1 - How to pass index path row of collectionView cell to segue

From the main controller that I have integrated collection view, I want to pass selected cell index path to another view controller (detail view)
so I can use it for updating a specific record.
I have the following working prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "RecipeDetailVC" {
let detailVC = segue.destinationViewController as? RecipeDetailVC
if let recipeCell = sender as? Recipe {
detailVC!.recipe = recipeCell
}
}
}
And I've tried including let indexPath = collection.indexPathForCell(sender as! UICollectionViewCell) but I get Could not cast value of type 'xxx.Recipe' (0x7fae7580c950) to 'UICollectionViewCell' at runtime.
I also have performSegueWithIdentifier("RecipeDetailVC", sender: recipeCell) and I wonder if I can use this to pass the selected cell's index path but not sure I can add this index to the sender.
I am not clear about the hierarchy of your collectionViewCell. But I think the sender maybe not a cell. Try to use
let indexPath = collection.indexPathForCell(sender.superView as! UICollectionViewCell)
or
let indexPath = collection.indexPathForCell(sender.superView!.superView as! UICollectionViewCell)
That may work.
I've wrote up a quick example to show you, it uses a tableView but the concept is the same:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var things = [1,2,3,4,5,6,7,8] // These can be anything...
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let objectForCell = self.things[indexPath.row] // Regular stuff
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let objectAtIndex = self.things[indexPath.row]
let indexOfObject = indexPath.row
self.performSegueWithIdentifier("next", sender: indexOfObject)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "next" {
// On this View Controller make an Index property, like var index
let nextVC = segue.destinationViewController as! UIViewController
nextVC.index = sender as! Int
}
}
}
Here you can see you get the actual object itself and use it as the sender in the perform segue method. You can access it in prepareForSegue and assign it directly to a property on the destination view controller.

Reciever has no segue with identifier swift 2

I am trying to pass value from Realm via segue to another viewcontroller. The problem is it is crashing with error : Reciever has no segue with identifier. I tried other way, prepareforsegue but it is not working as well as this one.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let lektire = datasource[indexPath.row]
let destinationVC = DetaljiViewController()
destinationVC.programVar = lektire.ime
destinationVC.performSegueWithIdentifier("oLektiri", sender: self)
}
However, my segue is definitely there, and its identifier is properly set (see screen shot below).
What is going on, and how can I fix this problem?
I believe this is what you are looking for you have to call performSegueWithIdentifier from the view controller you are currently inside of and either make the sender the indexPath or you can make it your datasource item.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier("oLektiri", sender: indexPath)
// Another WAY
self.performSegueWithIdentifier("oLektiri", sender: datasource[indexPath.row])
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "oLektiri") {
let indexPath = sender as! NSIndexPath
let toViewController = segue.destinationViewController as? NextViewController
let lektire = datasource[indexPath.row]
toViewController?.selectedObject = lektire
}
// Another WAY
if(segue.identifier == "oLektiri") {
let lektire = sender as? lektireTYPE
let toViewController = segue.destinationViewController as? NextViewController
toViewController?.selectedObject = lektire
}
}
// An example of what the next view controller should look like
class NextViewController: UIViewController {
var selectedObject: ObjectType?
}

Resources