How can I use prepare for segue in a UITableViewCell class - swift

I want send data between UITableViewCell and UIViewController
I have a list of address, in my cell I have a button (edit) and I want to send all the information about the address in the cell
#IBOutlet weak var address: UILabel!
#IBOutlet weak var rfc: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is ContainerVC{
let vc = segue.destination as? ContainerVC
vc.address = self.address.text
}
}
Method does not override any method from its superclass

Let's use segue inside your controller which contains the tableview which uses your UITableViewCell
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == segueIdentifier,
let destination = segue.destination as? ContainerVC,
let selectedIndex = tableView.indexPathForSelectedRow?.row
{
destination.address = self.addresses[selectedIndex].text
self.navigationController?.pushViewController(destination, animated: true) // For example
}
}

To pass data from cell Class to Controller Class use delegate methods on edit action.
Define call back delegate method in UITableViewCell class:
var callBack: ((NSDictionary) -> (Void))?
Use DS as per your need.
On Edit Action call delegate method and pass the data
#IBAction func editAction(_ sender: Any) {
callBack?(dictionary)
}
In cellForRowAt indexPath call delegate method with cell object
cell.callBack = { (currentData : NSDictionary)
in
// Perform Action on data
}
To Pass Data from Controller class to cell class you can simply define a method in cell class and can call in cellForRowAt indexPath.
Define SetData(dictionary) in cell class
In View Controller
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableViewOutlet.dequeueReusableCell(withIdentifier: "CellName", for: indexPath) as! CellClassName
cell.SetData(dict)
return cell
}

You need to do this from your UIViewController in which the UITableViewCell is implemented:
Assuming you have an array to populate your UITableView, the code would be something like:
import UIKit
class FirstViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var addresses = [String]()
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showAddressDetail" {
if let secondViewController = segue.destination as? SecondViewController {
secondViewController.address = sender as! String
}
}
}
}
extension FirstViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.addresses.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: YourTableViewCell = tableView.dequeueReusableCell(withIdentifier: "YourTableViewCell", for: indexPath) as! YourTableViewCell
cell.address = self.addresses[indexPath.row]
cell.delegate = self
return cell
}
Since you have to call your function from an action triggered by a button on your UITableViewCell, you can implement a protocol on your cell:
import UIKit
protocol YourTableViewCellDelegate: class {
func selectedAddress(address: String)
}
class YourTableViewCellDelegate: UITableViewCell {
weak var delegate: YourTableViewCellDelegate?
var address: String
}
And in your button action, call your delegate method like this:
#IBAction func selectAddress() {
self.delegate?.selectedAddress(address: self.address)
}
This will trigger the delegate on your UIViewController. To handle the call, don't forget to assign your cell delegate to your view controller and implement your cell delegate in your controller:
extension FirstViewController: YourTableViewCellDelegate {
func selectedAddress(address: String) {
// Do stuff with the selected address
}
}

Related

How to print .text from a label displayed in a tableView cell?

I have a table view populated through a Nib.
Below is the .xib
And the .swift
import UIKit
class PeopleCell: UITableViewCell {
#IBOutlet weak var peopleCellLabel: UILabel!
#IBOutlet weak var labelTest: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
#IBAction func button(_ sender: UIButton) {
labelTest.text = "Y"
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
In my PeopleDetailsController, how can I retrieve label.text ?
Below code only return "Label" (which is the default value) even if the button was pressed.
extension PeopleDetailsController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifierPeople, for: indexPath) as! PeopleCell
print(cell.labelTest.text)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
}
}
If you were to do this, you’d retrieve the cell via cellForRow(at:) (not to be confused with the delegate method tableView(_:cellForRowAt:)) which returns the cell associated with an indexPath (assuming that cell is visible):
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let cell = tableView.cellForRowAt(at: indexPath) as? PeopleCell else {
return
}
print(cell.labelTest.text)
}
But this pattern suggests that you’re storing model data within a view, which can be problematic. What if the row scrolled out of view and was discarded or reused? If you scroll it back into view, your string value has been lost.
Your button action handler should trigger the update of the model. Then didSelectRowAt would look up the value in the model, not in the cell.

How to add delegate value to tableview in swift

Here I am sending secondVC textfield value to firstVC tableview but I am not receiving delegate value in tableview.
Here is my code:
In firstVC:
import UIKit
class CreateBusinessViewController: UIViewController, MyDataSendingDelegateProtocol, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var iteamsArray = [String]()
override func viewDidLoad() {
super.viewDidLoad()
}
func sendDataToFirstViewController(myData: String) {
self.iteamsArray.append(myData)
print(iteamsArray)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "addSegue") {
let vc = segue.destination as! CreatePopUpViewController
vc.delegate = self
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return iteamsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! BusinessTableViewCell
cell.nameLabel.text = iteamsArray[indexPath.row]
return cell
}
}
In secondVC:
protocol MyDataSendingDelegateProtocol {
func sendDataToFirstViewController(myData: String)
}
class CreatePopUpViewController: UIViewController {
var delegate: MyDataSendingDelegateProtocol?
#IBOutlet weak var addTf: UITextField!
#IBAction func saveButn(_ sender: Any) {
self.delegate?.sendDataToFirstViewController(myData: addTf.text!)
dismiss(animated: true, completion: nil)
}
}
How to add secondVC textfield data to firstVC array?
To Reflect the data to tableview cell you just need to reload the table view like i have updated your function check below code.
func sendDataToFirstViewController(myData: String) {
self.iteamsArray.append(myData)
tableView.reloadData()
print(iteamsArray)
}
Hope this way may help you.

Nothing Happens When UITableView is Clicked (trying to send data between views)

So I have a two page app. The purpose of the app being the user can store expenses. They log a name and amount (attributes) and this data is stored in Expenses (entity). I have figured out how to create core data values, delete and retrieve. I am now working on updating. This will work by the user tapping on a table in the first view (ExpensesViewController) where the expenses are stored and this takes them to the 2nd view (EditExpensesViewController) where they can update the value back into core data. I am stuck on this 'data transfer' between the views.
I am using the storyboard and connected the first view to the second via 'show' set the segue identifier as 'editExpense'. However nothing happens when the table row is tapped. Any idea why it's not working and what I may have missed out? See here for GIF
ExpensesViewController
import UIKit
import CoreData
class ExpensesViewController: UIViewController {
#IBOutlet weak var totalLabel: UILabel!
#IBOutlet weak var tableView: UITableView!
var expenses_array = [Expenses]()
var send_array = [Expenses]()
override func viewDidLoad(){
super.viewDidLoad()
retrieveExpenses()
}
func retrieveExpenses(){
let fetchRequest: NSFetchRequest<Expenses> = Expenses.fetchRequest()
do {
let expenses = try PersistenceService.context.fetch(fetchRequest)
self.expenses_array = expenses
self.tableView.reloadData()
} catch {
print(error.localizedDescription )
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "editExpense") {
let secondViewController = segue.destination as! EditExpensesViewController
secondViewController.send_array = send_array
}
}
}
extension ExpensesViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return expenses_array.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
cell.textLabel?.text = expenses_array[indexPath.row].name
cell.detailTextLabel?.text = expenses_array[indexPath.row].amount
return cell
}
func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
let fetchRequest: NSFetchRequest<Expenses> = Expenses.fetchRequest()
do {
let result = try PersistenceService.context.fetch(fetchRequest)
// Delete from Core Data and remove from the arrays then save
if result.contains(expenses_array[indexPath.row]){
PersistenceService.context.delete(expenses_array[indexPath.row])
expenses_array = expenses_array.filter { $0 != expenses_array[indexPath.row] }
PersistenceService.saveContext()
self.getTotalExpenses()
self.tableView.reloadData()
}
} catch {
print(error.localizedDescription )
}
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
send_array = [self.expenses_array[indexPath.row]]
self.performSegue(withIdentifier: "editExpense", sender: self)
}
}
EditExpensesViewController
import UIKit
import CoreData
class EditExpensesViewController: UIViewController {
var send_array = [Expenses]() // Defined from the previous view controller
override func viewDidLoad() {
super.viewDidLoad()
print(send_array)
}
}
First of all conform to tableView delegates and dataSource in your viewDidLoad() :
tableView.delegate = self
tableView.dataSource = self
Delete segue from stroyboard and we will present the controller in code using :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "editExpense") {
let secondViewController = segue.destination as! EditExpensesViewController
secondViewController.send_array = send_array
// "someIdentifier" is the identifier of secondController in storyboard
storyboard?.instantiateViewController(withIdentifier: "someIdentifier")
present(secondViewController, animated: true, completion: nil)
}
}
Be aware to put storyboard identifier for second controller in storyboard using attribute inspector
The problem is that your first view controller is the UITableViewDataSource only. That is not enough. It needs to be the UITableViewDelegate too. didSelectRowAt Is a delegate method, not a data source method, and will not be called unless this view controller is the table views delegate and is explicitly declared as conforming to UITableViewDelegate.

Data not passing in Swift

I'm working on a journal app and now I want people to be able to click on a cell/entry and then go to a more detailed view where they can read/edit their entry. The thing is, the strings I'm trying to pass to the next view aren't coming through.
Edit:
This is the code on the TableViewController:
class TableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var items: [Entry] = []
var passedEntry:String = ""
func fetchData() {
do {
items = try context.fetch(Entry.fetchRequest())
print(items)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Couldn't fetch data")
}
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as UITableViewCell
let date = items.reversed()[indexPath.row].date
cell.textLabel?.text = items[indexPath.row].title
if let date = date {
let dateStamp = "Added on \(date)"
cell.detailTextLabel?.text = dateStamp
}
return cell
}
#objc(tableView:editActionsForRowAtIndexPath:) func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
let delete = UITableViewRowAction(style: .default, title: "Delete") { (action, indexPath) in
// delete item at indexPath
let item = self.items[indexPath.row]
self.context.delete(item)
(UIApplication.shared.delegate as! AppDelegate).saveContext()
self.items.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
return [delete]
}
public func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "showEntry") {
// initialize new view controller and cast it as your view controller
let viewController = EntryViewController()
// your new view controller should have property that will store passed value
viewController.passedEntry = passedEntry
}
}
public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
passedEntry = items[indexPath.row].title!
self.performSegue(withIdentifier: "showEntry", sender: self)
}
override func viewWillAppear(_ animated: Bool) {
fetchData()
}
This is the code on the EntryViewController which still prints nil:
class EntryViewController: UIViewController {
var passedEntry:String!
#IBOutlet weak var dreamTitle: UITextView!
#IBOutlet weak var dreamPost: UITextView!
#IBAction func saveChanges(_ sender: Any) {
}
override func viewWillAppear(_ animated: Bool) {
print(passedEntry)
}
I'm sorry for being a newbie, but I need some more detailed instructions...
Two issues:
The signature of prepareForSegue is wrong for Swift 3.
Never create view controllers with the default initializer when using storyboard
This main issue occurs in this line:
let viewController = EntryViewController()
You are creating a brand new instance of EntryViewController which is not the controller in the storyboard. You have to use the destination view controller of the segue:
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showEntry" {
// initialize new view controller and cast it as your view controller
let viewController = segue.destination as! EntryViewController
// your new view controller should have property that will store passed value
let title = items[indexPath.row].title
viewController.passedEntry = title
}
}
And I recommend to declare the string in the destination view controller as non-optional empty string rather than an implicit unwrapped optional. Non-optional types will never crash the app.
var passedEntry = ""
You are defining prepareForSegue() as a local function inside the tableView-function. It will not be used. Put a debugger breakpoint into it and see yourself if it will be reached.

Send data from TableView to DetailView Swift

I'm trying to do maybe one of the simplest and more confusing things for me until now
I wanna develop my own App , and in order to do it I need to be able to passing some information depending of which row user click (it's Swift lenguage)
We have a RootViewController(table view) and a DetailViewController (with 1 label and 1 image)
(our view)
Here is the code:
#IBOutlet weak var tableView: UITableView!
var vehicleData : [String] = ["Ferrari 458" , "Lamborghini Murcielago" , "Bugatti Veyron", "Mercedes Benz Biome"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var nib = UINib(nibName: "TableViewCell", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "cell")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return vehicleData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:TableViewCell = self.tableView.dequeueReusableCellWithIdentifier("cell") as TableViewCell
cell.lblCarName.text = vehicleData[indexPath.row]
cell.imgCar.image = UIImage(named: vehicleData[indexPath.row])
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
performSegueWithIdentifier("DetailView", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "DetailView") {
var vc = segue.destinationViewController as DetailViewController
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 100
}
Custom TableViewCell class (has a xib File with cell)
class TableViewCell: UITableViewCell {
#IBOutlet weak var lblCarName: UILabel!
#IBOutlet weak var imgCar: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
class DetailViewController: UIViewController {
#IBOutlet weak var lblDetail: UILabel!
#IBOutlet weak var imgDetail: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
The question is:
if user click Ferrari 458 , the lblDetail in DetailViewController would show: Ferrari 458 is a super car which is able to reach 325 km/ h ...... (whatever we want)
and imgDetail would be able to show an image (whatever we want) of the car
If user click Bugatti Veyron now the lblDetail show us: Bugatti Veyron is a perfect and super sport machine. It's one of the fastest car in the world....
imgDetail show us an image of this car
Same thing with all cars depending which row we have clicked
I know the work is around prepareForSegue func in first View Controller but i was trying a lot of different ways to make it possible and anything runs ok
How we can do this???
Here is the example for you:
var valueToPass:String!
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
println("You selected cell #\(indexPath.row)!")
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRowAtIndexPath(indexPath)! as UITableViewCell
valueToPass = currentCell.textLabel.text
performSegueWithIdentifier("yourSegueIdentifer", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "yourSegueIdentifer") {
// initialize new view controller and cast it as your view controller
var viewController = segue.destinationViewController as AnotherViewController
// your new view controller should have property that will store passed value
viewController.passedValue = valueToPass
}
}
But don't forget to create a passedValue variable into your DetailViewController.
This is just an example of passing data from one viewController to another and you can pass data with this example as you need.
And for more info refer this links.
Passing values between ViewControllers based on list selection in Swift
Use didSelectRowAtIndexPath or prepareForSegue method for UITableView?
Swift: Pass UITableViewCell label to new ViewController
https://teamtreehouse.com/forum/help-swift-segue-with-variables-is-not-working
May be this will help you.
Swift 3.0
var valueToPass:String!
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow!
let currentCell = tableView.cellForRow(at: indexPath)! as UITableViewCell
valueToPass = currentCell.textLabel?.text
performSegue(withIdentifier: "yourSegueIdentifer", sender: self)
}
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?){
if (segue.identifier == "yourSegueIdentifer") {
// initialize new view controller and cast it as your view controller
var viewController = segue.destination as! AnotherViewController
// your new view controller should have property that will store passed value
viewController.passedValue = valueToPass
}
}
This may be another solution, without much code in didSelectRowAtIndexPath method.
Note that while it may look cleaner, and we do not need an extra variable valueToPass, it may not be a best practice, because the sender argument inside performSegue method is supposed to be the actual object that initiated the segue (or nil).
// MARK: UITableViewDelegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "goToSecondVC", sender: indexPath)
}
// MARK: UIViewController methods
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "goToSecondVC" {
if segue.destination.isKind(of: CarDetailsController.self) {
let secondVC = segue.destination as! CarDetailsController
let indexPath = sender as! IndexPath
secondVC.passedValue = carsArray[indexPath.row]
}
}
}
If you drag a segue from the prototype cell (in the Interface Builder) to your next View Controller and set its segue identifier to "Your Segue Identifier", you can also do it with this shortcut:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Your Segue Identifier" {
let cell = sender as! YourCustomCell
let vc = segue.destination as! PushedViewController
vc.valueToPass = cell.textLabel?.text // or custom label
}
}
And you also don't need the performSegueWithIdentifier() in the didSelectRowAtIndexPath(), nor this Table View method.
In PushedViewController.swift (the next View Controller):
var valueToPass: String!
override func viewDidLoad() {
super.viewDidLoad()
yourLabel.text = valueToPass
}
It's important to set the label's value after it initialized from the Storyboard. That means, you can't set the label in the previous View Controller's prepareForSegue() directly, therefore needing to pass it with valueToPass.
Its simple, am adding one statement to above answer.
To get the selected car name in detail view label,
lblDetail.text = passedValue
you can add this code of line in viewDidLoad() func of your detailed view. passedValue contains the name of car which user selected(assign in prepareForSegue) then you can assign to your detailedView label.
Hope it helps!!