Linking tableview cell information to a view controller - swift

I'm pretty new to Swift. I wish to reflect tableview cell data (title and description of a post) in a new view controller. Here's the code I put in the first view controller (which contains the tableview cell).
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let indexPath = tableView.indexPathForSelectedRow()!
let currentCell = tableView.cellForRowAtIndexPath(indexPath) as! PostCell!
let post = posts[indexPath.row]
valuetoPass = post.title
valuetoPass_desc = post.postDescription
performSegueWithIdentifier("seguetoVC", sender: self)
}
And this one...
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "seguetoVC" {
let viewController = segue.destinationViewController as! UpdateVC
viewController.toPassTitle = valuetoPass
viewController.toPassDesc = valuetoPass_desc
}
}
Here's a part of UpdateVC..Added the variables, toPassTitle and toPassDesc...And these are the lines I added to viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
self.postTxt.delegate = self
self.descTxt.delegate = self
descTxt.text = toPassTitle
postTxt.text = toPassDesc
}
Just wondering what I'm doing wrong-- viewController.toPassTitle and viewController.toPassDesc keep on returning a null value. Thanks in advance.

You can set the sender parameter to be whatever type you want, there's no reason for it to be self, i.e. the originating view controller if that's not what you need. In fact, there should never be a need to pass the originating view controller as the sender, because you can get it from segue.sourceViewController in prepareForSegue.
In the documentation for prepareForSegue, Apple suggests:
Because segues can be triggered from multiple sources, you can use the information in the segue and sender parameters to disambiguate between different logical paths in your app. For example, if the segue originated from a table view, the sender parameter would identify the table view cell that the user tapped. You could then use that information to set the data on the destination view controller.
Which sounds like exactly the situation you're in. Since you've already figured out the model object for your cell, we can pass that rather than the cell. So, you could call performSegueWithIdentifier("seguetoVC", sender: post), and then change up your prepareForSegue implementation to use the details from the post directly, e.g.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "seguetoVC" {
let viewController = segue.destinationViewController as! UpdateVC
if let post = sender as ? Post {
viewController.toPassTitle = post.title
viewController.toPassDesc = post.postDescription
}
}
}
You don't need to call tableView.cellForRowAtIndexPath(indexPath) as! PostCell! anywhere, and it could be fairly expensive, so don't do it if you don't use it. Your didSelectRowAtIndexPath method might look more like this:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let post = posts[indexPath.row]
performSegueWithIdentifier("seguetoVC", sender: post)
}
There's also a bunch more you could do to make it safer, so have a look at unwrapping optionals using constructs such as if let and guard let, which can also help you avoid force casting (!), and potentially crashing your app. I've done that in unwrapping sender to post, so you can see an example there.

You do not need to declare indexPath again. didSelectRowAtIndexPath already has indexPath. Update your codes in didSelectRowAtIndexPath method.
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
let post = posts[indexPath.row]
valuetoPass = post.title
valuetoPass_desc = post.postDescription
performSegueWithIdentifier("seguetoVC", sender: self)
}
On top of that, ensure that in your storyboard, you need to name your segue identifier with seguetoVC also.

Related

Delegate method to segue in tableviewcell

I have a delegate method where if I press a button in the tableview, it should segue to another view controller and pass along data but it doesn't seem to work.
func goToVC(uid: String) { //delegate method
performSegue(withIdentifier: "showVC", sender: self) //Do I need this
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showVC", sender: self)
self.tableView.deselectRow(at: indexPath, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showVC" {
if let indexPath = tableView.indexPathForSelectedRow {
let guestVC = segue.destination as! GuestViewController
guestVC.ref = userArray[indexPath.row].ref
}
}
class MainViewController: UIViewController {
// set the cell's delegate in the data source
// pass the object to the cell from the data source
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.mainViewControllerDelegate = self
cell.object = someArray[indexPath.row]
}
// this is the method that gets called by the cell through the delegate
func pushToViewController(object: YourDataObject) {
let destination = SomeViewController()
destination.object = object
navigationController?.pushViewController(destination, animated: true)
}
}
class TheTableViewCell: UITableViewCell {
// create a delegate and a data object
var mainViewControllerDelegate: MainViewController?
var object: YourDataObject?
// this is the method that gets called when the button in the cell is tapped
#objc func buttonAction() {
mainViewControllerDelegate?.pushToViewController(object: object)
}
}
I highly recommend that beginners do not use Interface Builder. The less you use it early, the quicker you will understand more. Interface Builder is fool's gold for beginners.
You dont need delegate method here. Delegate method can be used if you need to pass the value from the child view controller.
What you are doing is exactly right. Make sure you set the segue identifier in the story board correctly.
And one more thing dont set your table IBOutlet as default tableView try setting a name apt for that table like toDoTable, so it will easy to debug.

data is not transferring at the right time in my prepare for segue function

here is my code.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as! monthTableViewCell!;
valueToPass = (currentCell?.monthOutlet.text)!
print(valueToPass)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "toMonthVC") {
// initialize new view controller and cast it as your view controller
let viewController = segue.destination as! monthCellViewController
// your new view controller should have property that will store passed value
viewController.dataFromHoursVC = valueToPass
}
}
So basically I am trying to pass a value from one VC to another. the didSelectRow is working perfectly how expected. However, the prepare function is running late. For example, the first time the code is run, the second vc sees the passed value as nil. But when i go back and then do it again, it says the passed value, but the value is the one that was done before. So simply put it is acting like the prepare function is behind or being called late.
How did you set up your segue? It sounds like the value isn't being set before the segue is performed.
Try doing it this way:
After you give your segue an identification name set up your code like this:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as! monthTableViewCell!;
valueToPass = (currentCell?.monthOutlet.text)!
print(valueToPass)
// invoke the segue manually after value is set
self.performSegue(withIdentifier: "SEGUEID", sender: self)
}
Quick Warning
You really should be careful using force unwraps optionalVar! and force downcasts thing = otherThing as! Type. It's far better to always use if-let and guard-let statements or fail-able down casts as?. Using these will decrease the chances of developing a hard to find nil value bug.
This could happen if you set your tableview's data source and/or delegate to you view controller in interface builder. Depending on the rest of your code, initialization of the view controller may need to access the tableview's delegate or dataSource during initialization (i.e. before the controller is passed to the prepareForSegue function). When it is set in IB, the delegate (or dataSource) allow the functions to be called.

Swift 3 Segue Q!- Prototype Cell to Multiple VCs - Using didSelectRowAtIndexPath

I am relatively new to Swift - developing an app for my hospital (I'm a resident physician) that's essentially a mobile reference tool that I'm building with a series of tableViews.
PROBLEM: I cant get my Segues to work from my Prototype cells to multiple VCs.
My initial VC is a tableVC, with prototype cells that I built into two sections using a nested array. Seen here
The destination VCs are also TableVCs, I want each tableCell of the initialVC to segue to a different destination TableVC.
I was able to set some Segues from my initialTVC to the destinationTVCs, I also have embedded a Navigation Controller -->
Here's what my storyboard setup looks like
What I'm trying to do for the Segues:
Set up the segue ID that I gave each segue in storyboard using the prepareForSegue function
Use didSelectRowAtIndexPath, with some If statements specifying which cell I want to do which segue, to execute the segue.
Keep that Storyboard Navigation controller in place - love the functionality and easy implementation it provides considering I'm going to add one more level of these segues as "submenus" before getting to my detailView pages.
Here's my Swift 3 code:
// MainTableViewController.swift
import UIKit
class MainTableViewController: UITableViewController {
var MainTitleArray = [["Children and Pregnant Women", "Mental Health and Addiction", "Hunger and Food Insecurity", "Legal Aid", "Housing, Heat, and Emergency Assistance", "New Immigrants, Refugees, and the Underserved", "The Uninsured and the Underinsured"], ["Primers and the Private Sector", "Federal Programs", "Social Securty"]]
var SubTitleArray = [["Resources for the most vulnerable in the family", "The programs to help those who need it", "When every night is a worry about food on the table", "How to defend yourself in a complicated system", "HEalth follows you into the home", "Your patient needs help - can they sign up worry-free?", "What to do when your options are limited"], ["What you need to know","Medicare Medicaid - What are they?","Disability and Insurance"]]
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 90
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return MainTitleArray.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return MainTitleArray[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainCell", for: indexPath) as! MainTableViewCell
cell.MainTitleLabel.text = MainTitleArray[indexPath.section][indexPath.row]
cell.MainSubTitleLabel.text = SubTitleArray[indexPath.section][indexPath.row]
return cell}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if (indexPath.section == 0) && (indexPath.row == 0) {
self.performSegue(withIdentifier: "CPWSegue", sender: indexPath);
}
else { self.performSegue(withIdentifier: "MHASegue", sender: indexPath)
}}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CPWSegue" {
let destVC = segue.destination as! CPWTableViewController
destVC.VCName = "CPW"}
}
}
I build this successfully but the segues just arent working still. I almost would rather still have some error code to get something to work with! If anyone has any helpful suggestions/comments/links for further reading, would HUGELY appreciate it. Have read through TONS of other pages and documents to try and get this to work as simply as I want it to, but no dice so far. Thanks in advance for any help you can offer!
UPDATE on 4/20/17: I've edited the prepareForSegue function, even put in a "VCName" var in so I can call segue.destination (even though "VCName" is not used) - STILL not giving me a segue (no error msg, and its successfully building) and not sure what to try next. Any advice appreciated!
UPDATE on 4/26/17: I used some break points and found out that neither the prepare nor perform segue functions were firing. I rebuilt and made sure it was an override function of the superclass (no idea why it wouldnt let me do that before hand), and it worked! Special thanks to Sasaang for the help!
Your code looks good from what i can tell, and it should work with this minor change. Remember that sections and rows are indices starting at 0. You have the following:
if (indexPath.section == 1) && (indexPath.row == 1) {
self.performSegue(withIdentifier: "CPWSegue", sender: indexPath);
}
I suspect you are tapping on the first item in the first section and not seeing any transitions, but the code above actually refers to the second item in the second section. The "Federal Programs" cell. Change the code to the following to be able to tap the "Children and Pregnant Women" cell to segue to the next ViewController.
if (indexPath.section == 0) && (indexPath.row == 0) {
self.performSegue(withIdentifier: "CPWSegue", sender: indexPath);
}
Additioanlly i would suggest you save some code by adding all of your ViewController StoryBoard Id's into a similar array structure as your main title array, that way instead of many if-else statements in the didSelectRowAtIndexPath function, you can change it to simply:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegue(withIdentifier: identifierList[indexPath.section][indexPath.row], sender: indexPath);
}
UPDATE
Additional note on updated question. The prepareForSegue function is just there to stage the next scene before presenting it. Like for instance if there's a "name" field in the CPW viewController, you can set it in the function like so before presenting it:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CPWSegue" {
let nextView = segue.destination as? CPWView
nextView.name = "SOME NAME"
}
}
You don't call performSegue() there again, its already happening.

pass data from tableview to view controller

I am writing a Xcode program in Swift. I have a tableview controller with some labels and an image per cell. They have their data from a first view controller. So far so good. Now i would like the user to tap a cell which opens a new controller which contains the same label and image data as the cell. With the following code the new controller opens, nonetheless I don't no how to transfer the data. If someone could help me I would be so so grateful.
Here are the codes that i tend to use:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "TableView"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! TableView
let picture = pic[indexPath.row]
cell.label1.text = picture.name1
cell.photoImage.image = picture.photo
cell.label2.text = picture.name2
return cell
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
let cell = indexPath.row
self.performSegueWithIdentifier("segue", sender: cell)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "segue"{
var row = tableView.indexPathForSelectedRow
let viewController = segue.destinationViewController as! vc2
}
}
PS:I have a segue from the image to the new controller with the identifier "segue".
PPS: Of course i have tried the following method: Send data from TableView to DetailView Swift but when i run my program I get an error with the information that my labels were unexpectedly found nil
You should not need to override didSelectRowAtIndexPath or call performSegueWithIdentifier to do this. Connect your segue in the IB file dragging from a table view controller's cell to the second controller. You should then pass the data to the controller in prepareForSegue, in the segue.destinationViewController. You set the public properties on that destination controller.
Also make sure your labels in your prototype cell have been connected in IB. If they are they, should not be nil. Set a breakpoint on the cellForRowAtIndexPath to verify this.
Unless you are talking about the labels in your destination controller. These also need to be hooked up in IB. You would then set them in prepareForSegue. You would get the appropriate data from the
let viewController = segue.destinationViewController as! vc2
var path = tableView.indexPathForSelectedRow
viewController.destLabel.text = arrayData[path.row].myData // or whatever data you have from your model.

Get data from a cell with "willSelectRowAtIndexPath" in Swift2

I'm trying since two weeks with the help of many Tutorials to get and segue data from a TableView cell. But it seems it is impossible to do that in XCode. It is only possible to get the selectedRow of a cell, but I cannot read out the Text labels of a selected cell.
If a user selects a Cell I want to segue the value of a label in the selected cell to a new View Controller.
I can only submit the selected row to a new View Controller but not the value of a label in this selected cell.
func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
row = indexPath.row // here is the row the user has selcted
labelStringOfselectedCell = "??????" // how to retrieve data from a label in the cell?
return indexPath
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
let DestViewController : ViewControllerDetail = segue.destinationViewController as! ViewControllerDetail
DestViewController.seguedData = labelStringOfselectedCell
}
If you really wanted to do something like that, you can do something like:
func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let cell = tableView.cellForRowAtIndexPath(indexPath) // do not confuse this with the similarly named `tableView:cellForRowAtIndexPath:` method that you've implemented
let string = cell?.textLabel?.text
return indexPath
}
Clearly it depends upon whether you're custom cell subclass and what the label was, but that illustrates the basic idea.
Having said that, you should not do this. Your app should be following Model-View-Controller (MVC) programming paradigm. When determining what data to pass to the next scene, you should go back to the original model you used when originally populating the table, not referring to some control in the table.
For example, let's imagine that you populated the original cell by retrieving data from the objects model:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let object = objects[indexPath.row]
cell.textLabel?.text = object.name
return cell
}
Then you would just implement a prepareForSegue that retrieves the data from objects also (and you don't have to implement willSelectRowAtIndexPath at all):
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let destination = segue.destinationViewController as? ViewControllerDetail {
let object = objects[tableView.indexPathForSelectedRow!.row]
destination.seguedData = object.name
}
}
Clearly, this will change depending upon what your model was and how you originally populated the cell, but hopefully it illustrates the basic idea.