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

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.

Related

DidSelectRow function is not called

I'm trying to implement the didSelectRow function and perform a segue but when running the cells select and nothing happens.
I created a print statement that also doesn't run which proves that the function doesn't appear to be firing. Why would this be?
I have checked the identifier is correct and have researched these for a few hours going through many stack overflow threads but with little luck.
import UIKit
import CoreData
class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let viewController = ListNameViewController()
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var itemChoosen = 0
override func viewDidLoad() {
super.viewDidLoad()
homeListsTableView.delegate = self
homeListsTableView.dataSource = self
viewController.loadList()
}
#IBOutlet weak var homeListsTableView: UITableView!
#IBAction func templatesButton(_ sender: Any) {
tabBarController?.selectedIndex = 2
}
#IBAction func allListsButton(_ sender: Any) {
tabBarController?.selectedIndex = 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return viewController.listName.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let result = viewController.listName[indexPath.row]
cell.textLabel?.text = ("\(String(result.listName!))")
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
context!.delete(viewController.listName[indexPath.row])
viewController.listName.remove(at: indexPath.row)
viewController.saveList()
homeListsTableView.reloadData()
}
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "items2", sender: self)
print("selected")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(false)
viewController.loadList()
homeListsTableView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
homeListsTableView.reloadData()
}
}
ListNameViewController:
import UIKit
import CoreData
class ListNameViewController: UIViewController, UITableViewDelegate {
let context = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext
var listName : [ListName] = []
override func viewDidLoad() {
super.viewDidLoad()
createButtonChange.isEnabled = false
//Objective-C Line used to keep checking if the text field is vaild before enabling the submit button
listNameValue.addTarget(self, action: #selector(textValidation), for: UIControl.Event.editingChanged)
}
#IBOutlet weak var listNameValue: UITextField!
#IBOutlet weak var locationOption: UITextField!
#IBOutlet weak var createButtonChange: UIButton!
#objc func textValidation() {
//Text Field Validation check before button is enabled
if listNameValue.text!.isEmpty {
createButtonChange.isEnabled = false
} else {
createButtonChange.isEnabled = true
}
}
// Create a new List
#IBAction func createButton(_ sender: Any) {
let newList = ListName(context: context!)
newList.listName = listNameValue.text
saveList()
self.navigationController!.popViewController(animated: true)
viewWillAppear(false)
}
func saveList() {
do {
try context!.save()
} catch {
print("Error saving context \(error)")
}
}
func loadList() {
let request : NSFetchRequest<ListName> = ListName.fetchRequest()
do{
listName = try context!.fetch(request)
} catch {
print("Error loading categories \(error)")
}
}
//Pass data to the HomeViewController
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// let vc = segue.destination as! HomeViewController
}
}
// commented out core data and just used a normal array for testing.
Did you add segue in your storyboard for the tableview ? In this case the didSelect is not call but the prepare(for segue) of the tableview controller is called.
Ok solved it - There was a rouge tap gesture recognizer on the page. Removed it and works on one click. I have seen that if you wish to keep the gesture just add this line of code at the top of the function:
tap.cancelsTouchesInView = false
Took three days but I got there. Thanks for the help!

Aftar i click on cell its pass me 2 views, how can i fix it?

I have an app that starts with tab bar with 3 items,2 of them is just a regular view controller and one of them is navigation controller and after him is table view,when I click on a cell I want to pass 2 String to the labels and to pass to a new view controller to show the label's,my problem is that after I click on the cell its looks like its jump 2 view controller's until the view controller that I want, and in the first time I didn't see my data that I pass, only after I press to come back to the table view controller(here I need to back to him from 2 view controllers), please help me to fix my code, I want to pass the data and move to a new view controller and when I want to come back to the table view I need to click just back
//Table view controller, before of him I have a navigation controller
//The identifier of the segue is "Details"
import UIKit
class HistoryTableViewController:
UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var historyTableView: UITableView!
var historyArray = [history]()
var foods = ["Milk","Honey","Salt","Bread","Banana"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return foods.count
//historyArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = historyTableView.dequeueReusableCell(withIdentifier: "cell") as? HistoryCellViewController
else {return UITableViewCell()}
// cell.ageLabel.text = String(format:"%f", historyArray[indexPath.row].age)
// cell.genderLabel.text = historyArray[indexPath.row].gender
// cell.ageLabel.text = String(format:"%f", 23)
cell.genderLabel.text = foods[indexPath.row]
cell.ageLabel.text = foods[indexPath.row]
// cell.genderLabel.text = "Bar"
//cell.historyImage.image = nil
return cell
}
var selectedAge:String = " "
var selectedGender:String = " "
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedAge = self.foods[indexPath.row]
selectedGender = self.foods[indexPath.row]
performSegue(withIdentifier: "Details", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Details"{
let vc = segue.destination as! HistoryDetailsViewController
vc.age = selectedAge
vc.gender = selectedGender
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
foods.remove(at: indexPath.row)
historyTableView.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
//view controller that I want to pass after a click on a cell
import UIKit
class HistoryDetailsViewController: UIViewController {
#IBOutlet weak var ageLabel: UILabel!
#IBOutlet weak var genderLabel: UILabel!
var age:String?
var gender:String?
override func viewDidLoad() {
super.viewDidLoad()
ageLabel.text = self.age
genderLabel.text = self.gender
// Do any additional setup after loading the view.
}
//
// #IBAction func Back(_ sender: UIBarButtonItem) {
//
// // self.tabBarController?.selectedIndex = 0
// }
}
First of all, in tableView(_:cellForRowAt:) you're typecasting the dequeued cell to HistoryCellViewController.
My question is - what is the type of HistoryCellViewController? Is it a UITableViewCell or something else? In case it is a UITableViewCell you must rename it to something more relevant like HistoryCell. It is a bit confusing.
Approach-1:
Now coming to the actual problem you're facing. Instead of using the segue, try pushing the HistoryDetailsViewController manually like so,
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let controller = self.storyboard?.instantiateViewController(withIdentifier: "HistoryDetailsViewController") as? HistoryDetailsViewController {
controller.age = self.foods[indexPath.row]
controller.gender = self.foods[indexPath.row]
self.navigationController?.pushViewController(controller, animated: true)
}
}
There is no need to extra variables - selectedAge and selectedGender.
Approach-2:
In case you want to do it with the segue itself, then don't call performSegue(withIdentifier:sender:) in tableView(_:didSelectRowAt:), i.e. remove the below line of code,
performSegue(withIdentifier: "Details", sender: self)
This is because, the "Details" segue is being executed twice - once via storyboard and another via the above line of code.

Fatal error: Index out of range when click specific cells for segue

I am trying to create a segue so every time I click a tableView row, show another ViewController with the information of the specific row.
I populate my data from Firestore.
Basically every document contains an array and then I populate the strings of the array in rows
var ingredientsArray = [Ingredients]()
func numberOfSections(in tableView: UITableView) -> Int {
return ingredientsArray.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ingredientsArray[section].compName.count
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
performSegue(withIdentifier: "SearchDetails", sender: self)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "IngredientCell", for: indexPath) as! IngredientTableViewCell
cell.populate(ingredient: ingredientsArray[indexPath.section])
let item1 = ingredientsArray[indexPath.section].compName[indexPath.row]
cell.ingredientNameLabel.text = ("\(item1)")
return cell
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DetailViewController{
//HERE IS THE ERROR.
destination.ingredient = ingredientsArray[(tableView.indexPathForSelectedRow?.row)!]
}
}
When I click some rows then my app crashes and gives me Fatal error: Index out of range
DetailViewController
class DetailViewController: UIViewController {
#IBOutlet weak var compNameLbl: UILabel!
var ingredient : Ingredients?
override func viewDidLoad() {
super.viewDidLoad()
compNameLbl.text = "\((ingredient?.compName)!)"
}
}
Also when I try to show the name in the label the whole array appears.
Get the string value from the compName array and pass the value
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DetailViewController, let indexPath = tableView.indexPathForSelectedRow {
destination.ingredient = ingredientsArray[indexPath.section].compName[indexPath.row]
}
}
Change the ingredient type to String in DetailViewController
class DetailViewController: UIViewController {
#IBOutlet weak var compNameLbl: UILabel!
var ingredient : String?
override func viewDidLoad() {
super.viewDidLoad()
compNameLbl.text = ingredient
}
}

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.

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.