passing tapped cell data to another view via segue in Swift - ios

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]

Related

Prepare for segue in TableViewDelegate methods

[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

Swift 4.2 - How to call "indexPath.row" from "didSelectRowAtIndexPath" method in a tableView to another class?

I have two viewControllers. One has a tableView which has some cells from an array; And the other is a viewController which contains a webkit that I want to present some local HTML files. Also I defined the webKit page as tableView's next page.
What I want is, when a user chooses a cell in the tableView, according to which cell is selected, it goes to webkit Page, and some parts of codes runs for user to show a specific HTML. In other words, I have 5 HTML files and 5 items in tableViewCells
so if a user chooses for example the first item, the HTML1 shows to him, if he chooses the second cell, the HTML2 present for him etc.
I tried a lot of ways. but nothing happened. also tried to create instance object from the tableview class...
tried also some same problem here ins StackOverFlow but not succeed
First Page:
var arr = ["masoud" , "hossein", "reza" , "shiva" , "fateme"]
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("item selected is : \(indexPath.row)")
// if indexPath.row == 1 {
performSegue(withIdentifier: "TableSegue", sender: nil)
//}
tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch (segue.destination.view, segue.destination, sender) {
case let (_, controller as WebKitController, indexPath as NSIndexPath):
controller.indexPath = indexPath
break
default:
break
}
}
the Second Page:
import WebKit
class WebKitController: UIViewController, WKUIDelegate, WKNavigationDelegate {
#IBOutlet weak var myWebKit: WKWebView!
var reza : String = String()
var indexPath: NSIndexPath? {
didSet {
reza = (indexPath?.row.description)!
print(indexPath?.row as Any)
self.myWebKit.uiDelegate = self
self.myWebKit.navigationDelegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
let url = Bundle.main.url(forResource: "reza", withExtension: "html", subdirectory: "ReZeynoo2")!
myWebKit.loadFileURL(url, allowingReadAccessTo: url)
let request = URLRequest(url: url)
myWebKit.load(request)
self.myWebKit.uiDelegate = self
self.myWebKit.navigationDelegate = self
}
As an alternative approach
I would store not a simple array but struct of name and bool is selected or not
struct CellData {
let name: String!
let isSelected: Bool!
let index: Int!
init(name: String, isSelected: Bool, index: Int) {
self.name = name
self.isSelected = isSelected
self.index = index
}
}
Then in didSelectAtRow func will make selected the cell which tapped and use this info in prepare for segue func
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.deselectAll()
self.cellItems[indexPath.row].isSelected = true
performSegue(withIdentifier: "TableSegue", sender: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch (segue.destination.view, segue.destination, sender) {
case let (_, controller as WebKitController, indexPath as NSIndexPath):
if let item = cellItems.first(where: {$0.isSelected == true}) {
controller.indexPath = item.index
}
break
default:
break
}
}
private func deselectAll() {
for item in cellItems {
item.isSelected = false
}
}
You need to create a Int variable in WebKitController class as selectedRow where the selected row will get assigned from table view cell selection,
// MARK: - Table view delegates
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "TableSegue", sender: indexPath)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "TableSegue", let indexPath = sender as? IndexPath {
let webController = segue.destination as? WebKitController
webController.selectedRow = indexPath.row
}
}
I think you can pass your indexpath.row value in sender of your perfomSegue and your second view controller just use this
here is code
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "secondvc", sender: indexPath.row)
}
just send indexpath.row value in sender
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "secondvc"{
let vc = segue.destination as! SecondViewController
vc.row = sender as! Int
}
}

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.

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)
}

How to go to other view Controller using Navigation Bar Item?

This is my TableViewController: Cells will segue to First controller
import UIKit
class SubjectsTableViewCell: UITableViewCell {
#IBOutlet weak var subjectImage: UIImageView!
#IBOutlet weak var subjectName: UILabel!
}
class AddQuestionTableViewController: UITableViewController {
var Subjects = ["Geology", "Mathematics", "Computer", "English", "History", "Science"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (Subjects.count)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pickedSubjectSegue", sender: Subjects[indexPath.row])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let next = segue.destination as! SubViewController
next.myLabel = sender as! String
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SubjectsTableViewCell
cell.subjectName?.text = Subjects[indexPath.row]
cell.subjectImage?.image = UIImage(named: (Subjects[indexPath.row] + ".jpeg"))
return cell
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
How can I move to second view controller using the Add navigation bar item?
What I tried: ctrl + dragging then modal - failed.
Should I use action or segue? But i already use segue in the table cells.
Any help is greatly appreciated. Thanks
Connect seque to second view controller and give it a name other than the segue that is connected to first view controller i.e. pickedSubjectSegue
Example: "secondSegue"
Then in prepareSegue method check the identifiers of both segues before performing any operation:
Code:
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
{
if segue.identifier = "pickedSubjectSegue"
{
let next = segue.destination as! SubViewController
next.myLabel = sender as! String
}
else
{
//Write your code here
}
}

Resources