Prepare for segue in TableViewDelegate methods - ios

[SOLVED]
Let me explain my problem;
I have a bunch of Event object which appears in tableView cells.
And these objects store in events array
var events: [Event]
Also i have another view controller(DetailViewContoller), in that view controller i have a variable for receiving event object from tableview.
import UIKit
class EventDetailViewController: UIViewController {
var event: Event?
override func viewDidLoad() {
super.viewDidLoad()
print(event?.name)
}
When i tapped the cells i want to pass my current event object to variable(indicated on above) in DetailViewController. So i tried this ->
extension EventListViewcontroller:UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Whenever cells tapped, related object needs to pass DetailViewController
performSegue(withIdentifier: K.Segues.eventListToDetail, sender: self)
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == K.Segues.eventListToDetail {
let VC = segue.destination as! EventDetailViewController
VC.event = events[indexPath.row]
}
}
}
But unfortunately after segue performed when i checked the event variable in DetailViewController it was shown as a nil. So question is, how can i pass my tapped event cell to other view controller?
Note: i've found this topic How do you test UIStoryBoard segue is triggered by didSelectRow(at:) but it didn't help my problem...

In your class
class ViewController: EventListViewcontroller {
var selectedIndex: IndexPath? = nil
}
And change your code in didSelect as
extension ViewController:UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Whenever cells tapped, related object needs to pass DetailViewController
self.selectedIndex = indexPath
performSegue(withIdentifier: K.Segues.eventListToDetail, sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let indexPath = self. selectedIndex, segue.identifier == K.Segues.eventListToDetail {
let VC = segue.destination as! EventDetailViewController
VC.event = events[indexPath.row]
}
}
}
Hope this helps

Related

Variable returns nil value after changing its value from another swift file

I'm trying to change a value of a variable from another Swift file, but for some reason it does not work and it returns nil.
This is what I have tried:
class ShowIssues: UIViewController, UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let si = self.storyboard?instantiateViewController(withIdentifier: "ShowIssueDetail") as! ShowIssueDetail
si.idSelected = indexPath.row //Here I change the value of the variable
performSegue(withIdentifier: "ShowIssueDetail", sender: self)
}
}
ShowIssueDetail.swift:
class ShowIssueDetail: UITableViewController {
var idSelected: Int! //This is the variable I want to change its value from the another swift file
override func viewDidLoad() {
print(idSelected) //Here it prints out nil instead of the selected row
}
}
I have also tried it in this way, but same issue:
class ShowIssues: UIViewController, UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let si = ShowIssueDetail()
si.idSelected = indexPath.row //Here I change the value of the variable
performSegue(withIdentifier: "ShowIssueDetail", sender: self)
}
}
What am I doing wrong?
Thank you in advance!
Note: Both swift files are of different type, ShowIssues.swift is UIViewController and ShowIssueDetail is UITableViewController, I do not know if it does not work due to this.
If you have a segue set up in Storyboard, you shouldn't be instantiating the destination view controller from code, the Storyboard will create the view controller for you. By initialising another instance, you end up setting values on an instance that won't be presented to the user.
You need to override prepare(for:) and set the value there.
class ShowIssues: UIViewController, UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "ShowIssueDetail", sender: indexPath.row)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowIssueDetail", let destinationVC = segue.destination as? ShowIssueDetail, let selectedId = sender as? Int {
destinationVC.idSelected = selectedId
}
}
}
performSegue will create a new instance of ShowIssueDetail.
Because of that you never set idSelected
It looks like you're talking about the variable idSelected in your ShowIssueDetail view controller.
The problem is that in both versions of your code you are creating a different instance of the view controller than the one you are segueing to, and setting a value in that throw-away view controller.
You need to use prepareForSegue to pass the variable to the view controller that you're segueing to.
//Add an instance variable to remember which item is selected.
var selected: Int?
class ShowIssues: UIViewController, UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selected = self.storyboard?instantiateViewController(withIdentifier: "ShowIssueDetail") as! ShowIssueDetail
performSegue(withIdentifier: "ShowIssueDetail", sender: self)
}
}
func prepare(for: segue, sender: Any) {
if let dest = segue.destination as? ShowIssueDetail {
dest.idSelected = selected
}
}
Your approach is wrong here:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let si = self.storyboard?instantiateViewController(withIdentifier: "ShowIssueDetail") as! ShowIssueDetail
si.idSelected = indexPath.row //Here I change the value of the variable
performSegue(withIdentifier: "ShowIssueDetail", sender: self)
}
You are creating another instance of the VC and setting the value. That VC is not the actual one that will be shown.
You need to use the prepareForSegue method
var selectedRow: someTypeSameAsRow?
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedRow = indexPath.row
performSegue(withIdentifier: "ShowIssueDetail", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowIssueDetail",
let vc = segue.destination as? ShowIssueDetail {
vc.idSelected = selectedRow
}
}
EDIT:
What you may need to do to resolve the error mentioned in the comments is wait until the detail view is loaded properly. The awakeFromNib function should work here:
// this goes in the view controller
override func awakeFromNib() {
super.awakeFromNib()
self.detailView.selected = selected
}
So, with this code you are waiting until the view of the VC and it's subviews are fully loaded and then setting the selected property of showDetailView to the same selected property that is on the VC.

passing tapped cell data to another view via segue in Swift

I have a tableView and I am trying to send the data related to tapped cell to another view via segue.
My code works only after second tap, and always returning nil at first tap. I suppose for some reason prepareForSegue gets executed first, that's why detailToSend is always nil at the first tap, and with the second tap I get the data from the previous tapped cell. How can I fix this ?
Updated:
var detailToSend = SingleRepository()
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if filteredResult.count > 0 {
detailToSend = filteredResult[indexPath.row]
} else {
detailToSend = finalArrayUnwrapped[indexPath.row]
}
performSegue(withIdentifier: "showDetailSegue", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetailSegue" {
let vc = segue.destination as! DetailViewController
vc.detail = detailToSend
}
}
My DetailViewController
class DetailViewController: UIViewController {
#IBOutlet weak var detailLabel: UILabel!
var detail: SingleRepository?
override func viewDidLoad() {
super.viewDidLoad()
if let detailUnwrapped = detail {
print(detailUnwrapped)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Make sure that you have created your segue from the view controller object in the storyboard and not the cell; if you create the segue from the cell, then the segue will fire before didSelectRowAt is called.
Once you have set the segue correctly, you can use the sender parameter to avoid the use of the detailToSend property:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var detailToSend: SingleRepository
if filteredResult.count > 0 {
detailToSend = filteredResult[indexPath.row]
} else {
detailToSend = finalArrayUnwrapped[indexPath.row]
}
performSegue(withIdentifier: "showDetailSegue", sender: detailToSend)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let vc = segue.destination as? DetailViewController, let detailToSend = sender as? SingleRepository {
vc.detail = detailToSend
}
}
You could setup a manual segue in the storyboard and trigger it on tap.
So instead of segueing from a cell to a controller, segue from a controller to another controller.
and then your code will be something like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if filteredResult.count > 0 {
detailToSend = filteredResult[indexPath.row]
} else {
detailToSend = finalArrayUnwrapped[indexPath.row]
}
performSegue(withIdentifier: "showDetailSegue", sender: nil)
}
prepareForSegue should remain exactly the same.
Also check your didSelectRowAt method. You are ignoring everything that happens in scope of
if filteredResult.count > 0 {
detailToSend = filteredResult[indexPath.row]
}
because after that you assign
detailToSend = finalArrayUnwrapped[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)
}

Swift - pass data from tableview to third viewcontroller

I have too much code to paste it all in, so I'm going to be vague. I think I just need a general idea of what to do, and then I'll be able to do it.
I have a beautiful table built with a list of local businesses. When you click on one, it opens up the DetailView, with some great information about the business. I have a MAP button on the DetailView that triggers Thirdview with a Mapview inside of it.
I'm having issues retrieving data from the cells in the TableView in the Thirdview.
My views:
Tableview => DetailView => Thirdview
In my Thirdview, can I call the information from Tableview?
Or is it easier to pass the information from DetailView to Thirdview?
Thanks!
TableView
var objects: [MyObject] = []
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let myObject = objects[indexPath.row]
}
override func prepare(for segue: UIStoryboardSegue, sender: Any) {
if segue.identifier == "mySegue" {
let detailVC = segue.destination as! MyDetailVC
detailVC.dataFromTableView = myObject
}
}
Detail View
var dataFromTableView = MyObject()
override func prepare(for segue: UIStoryboardSegue, sender: Any) {
if segue.identifier == "mySegue" {
let thirdVC = segue.destination as! MyThirdViewController
thirdVC.dataFromDetailView = dataFromTableView
}
}
Third View
var dataFromDetailView = MyObject()
override func viewDidLoad() {
print(dataFromDetailView)
}

Swift 2 - prepareForSegue nil Value

I am trying to send a link over another view when a select a row in UITableView.
When I select the row I can see the link is printed however, the value does not get to the function prepareForSegue.
It seems like the prepareForSegue function is called before the selection row in UITableView.
var videouserPath = ""
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
videouserPath = self.arrayUserVideo[indexPath.row][4]
print("tableview: ----\(videouserPath)")
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
print("segue: ----\(videouserPath)")
if segue.identifier == "toUserVideo"
{
if let destinationVC = segue.destinationViewController as? GetVideo_ViewController{
destinationVC.videoToDisplay = videouserPath
}
}
}
I got in debug:
segue: ----
tableview: ----https://example.com/resources/1361929513_02-02-2016_125830.mov
Why is the segue function called before selection?
When linking UIStoryboardSegue to UITableViewCell from the storyboard, the messaging is in the order prepareForSegue, didSelectRowAtIndexPath.
Automatic Method
Bypass didSelectRowAtIndexPath altogether.
For general purpose and single selection cells, you do not need to implement didSelectRowAtIndexPath. Let the segue associated with UITAbleViewCell do the work, and handle selection like so:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailSegueIdentifier" {
if let indexPath = tableView.indexPathForSelectedRow {
print(indexPath)
}
}
}
Manual Method
If you absolutely need to do extra work before the segue, do not associate it to the UITableViewCell but to the UITableViewController. You will then need to trigger it programmatically. Inspiration here.
In IB, assign the segue to table view controller, give it an identifier (say detailSegueIdentifier), and invoke it like so.
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("didSelectRowAtIndexPath")
self.performSegueWithIdentifier("detailSegueIdentifier", sender: nil)
}
Passing parameters to the segue:
Invoking performSegueWithIdentifier will also give you a chance to explicitly pass parameters, not second-guess indexPathForSelectedRow, and not rely on a global(*).
override func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) {
print("didSelectRowAtIndexPath")
self.performSegueWithIdentifier("detailSegueIdentifier", sender: indexPath)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "detailSegueIdentifier" {
if let indexPath = sender as? NSIndexPath {
let name = db[indexPath.row].key
if let controller = segue.destinationViewController as? DetailViewController {
controller.name = name
}
}
}
}
(*) Don't ever rely on globals if you can help it.

Resources