So I have recently coded a search bar programmatically using tableview and navigation control. I am now having trouble finding information on how to segue the search bar after the user clicks on an item in the search bar.
I have tried using a view controller but that has not worked. I think my best bet is to do it programmatically.
EDIT There is no function yet in this code to implement a display screen. Im wondering what code is needed (im a very new developer) to get to another screen after clicking an element in the search bar. anything helps!!***
import UIKit
class SearchTableViewController: UITableViewController, UISearchBarDelegate {
let searchBar = UISearchBar()
let tableData = ["Boston University", "Boston College", "Northeastern University", "Suffolk University", "American University", "Harvard University", "Massachusetts Institute of Technology", "Tufts University", "Berklee College of Music", "Emerson College"]
//variables added for search function
var filteredArray = [String()]
var shouldShowSearchResults = false
override func viewDidLoad() {
super.viewDidLoad()
createSearcherBar()
}
func createSearcherBar() {
searchBar.showsBookmarkButton = false
searchBar.placeholder = "Search College"
searchBar.delegate = self
self.navigationItem.titleView = searchBar
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
filteredArray = tableData.filter({ (names: String) -> Bool in
return names.range(of: searchText, options: .caseInsensitive) != nil
})
if searchText != "" {
shouldShowSearchResults = true
self.tableView.reloadData()
}
else{
shouldShowSearchResults = false
self.tableView.reloadData()
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if shouldShowSearchResults {
return filteredArray.count
}
else{
return tableData.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "basicCell", for: indexPath)
if shouldShowSearchResults {
cell.textLabel!.text = filteredArray[indexPath.row]
return cell
}
else{
cell.textLabel!.text = tableData[indexPath.row]
return cell
}
}
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
searchBar.endEditing(true)
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
shouldShowSearchResults = true
searchBar.endEditing(true)
self.tableView.reloadData()
}
/*
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
Hopefully I can get a user to click on one of the options in a search bar, and it brings them to a different page.
First of all your method textDidChange is unnecessarily expensive because you are always filtering the array even if the search text is empty. This is more efficient
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchText.isEmpty {
shouldShowSearchResults = false
filteredArray.removeAll() // good practice to release memory when the search is finished
} else {
filteredArray = tableData.filter{ $0.range(of: searchText, options: .caseInsensitive) != nil }
shouldShowSearchResults = true
}
self.tableView.reloadData()
}
Second of all the declaration of filteredArray is slightly wrong. The parentheses must be outside the brackets. Your syntax declares a string array containing one empty string.
var filteredArray = [String]()
To answer your question implement didSelectRowAt and add the same shouldShowSearchResults logic to distinguish the arrays. Call performSegue and pass the string as sender
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = shouldShowSearchResults ? filteredArray[indexPath.row] : tableData[indexPath.row]
performSegue(withIdentifier: "MyIdentifier", sender: item)
}
and get it in prepare(for segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
guard segue.identifier == "MyIdentifier" else { return }
let destinationController = segue.destination as! MyViewController
let item = sender as! String
...
Related
When I use trailingSwipeActionsConfigurationForRowAt my TableView will show the delete and reorder options, however when selecting reorder nothing happens. I think I have all of the correct methods and am calling setEditing; is there anything else I'm missing? Thanks!
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
let tableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
setupTableView()
}
func setupTableView() {
tableView.frame = self.view.frame
tableView.dataSource = self
tableView.delegate = self
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
tableView.dragInteractionEnabled = true
self.view.addSubview(tableView)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 8
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.backgroundColor = .gray
cell.showsReorderControl = true
return cell
}
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
self.tableView.setEditing(editing, animated: animated)
}
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .normal, title: "delete") { (action, view, completion) in
tableView.reloadData()
completion(true)
}
let reorderAction = UIContextualAction(style: .normal, title: "reorder") { (action, view, completion) in
tableView.setEditing(true, animated: true)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction, reorderAction])
}
}
class CustomCell: UITableViewCell {
}
Result after swiping:
After selecting reorder:
A few observations:
You are not going to get the reorder controls if you do not implement tableView(_:moveRowAt:to:), e.g., assuming you had a model which was an array called objects, you could do the following:
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let object = objects.remove(at: sourceIndexPath.row)
objects.insert(object, at: destinationIndexPath.row)
}
The trailingSwipeActionsConfigurationForRowAt is probably not the right place to put a “reorder” command. Part of the reason is that once the table view is in edit mode and you tap on the ⛔️, the trailing actions show up, and “reorder” does not make sense in that context. E.g., here I am tapping on ⛔️ and I see the confusing actions.
I would suggest only adding “delete” as the trailing action. That way, you (a) get only “delete” if you tap on ⛔️ in isEditing mode, but also (b) get the stand-alone swipe action, too.
You cannot initiate isEditing from the trailing swipe actions (and, as discussed above, I do not think you want to, anyway). So, if you do not have “reorder” in the trailing swipe actions, you need some other method to enter edit mode. E.g., above, I added an “edit” button to the navigation bar that toggles isEditing:
#IBAction func didTapEdit(_ sender: Any) {
tableView.isEditing.toggle()
}
Then, you can keep the swipe to delete functionality, but when you tap on edit button, you have the tap on ⛔️ to delete functionality (plus the handles for reordering because we added tableView(_:moveRowAt:to:) as outlined in step one, above):
Another way to achieve reordering is to just allow drag and drop within the table view where you can long-press on a row and then drag it:
This is enabled by setting dragInteractionEnabled and dropDelegate:
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
let formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .spellOut
return formatter
}()
private var objects: [Foo] = ...
override func viewDidLoad() {
super.viewDidLoad()
...
tableView.dragInteractionEnabled = true
tableView.dropDelegate = self
}
}
// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource { ... }
// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let deleteAction = UIContextualAction(style: .destructive, title: "delete") { [weak self] action, view, completion in
self?.objects.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .middle)
completion(true)
}
return UISwipeActionsConfiguration(actions: [deleteAction])
}
// This is used if table view is in `isEditing` mode and by `UITableViewDropDelegate`
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let object = objects.remove(at: sourceIndexPath.row)
objects.insert(object, at: destinationIndexPath.row)
}
}
// MARK: - UITableViewDropDelegate
extension ViewController: UITableViewDropDelegate {
func tableView(_ tableView: UITableView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
guard
session.items.count == 1, // Accept only one drag item ...
tableView.hasActiveDrag // ... from within this table view
else {
return UITableViewDropProposal(operation: .cancel)
}
return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
}
func tableView(_ tableView: UITableView, performDropWith coordinator: UITableViewDropCoordinator) {
guard let destinationIndexPath = coordinator.destinationIndexPath else { return }
for item in coordinator.items {
if let sourceIndexPath = item.sourceIndexPath {
DispatchQueue.main.async {
tableView.moveRow(at: sourceIndexPath, to: destinationIndexPath)
}
}
}
}
}
Clearly, if you were going to enable drag from this app to others, you would add UITableViewDragDelegate conformance here, and make your model objects conform to NSItemProviderReading and NSItemProviderWriting. But the above should be sufficient for dragging and dropping to reorder within a UITableView.
I'm new to this, I've just made an indexed table view "groups search" with all groups in my app and got a problem: when I'm trying to add some group to the "my groups" view, there are should appear a selected group, but actually I got the first one from all groups array instead. Also I can't add several items started with a similar letter in the "my groups". It might be stupid, but I have no idea how to fix that. Thank you!
import UIKit
final class AllGroupsViewController: UITableViewController {
var groups = [
"cats",
"birds",
"dogs",
"books",
"music",
"movies",
"art",
"science",
"tech",
"beauty",
]
var groupSectionTitles = [String]()
var groupsDictionary = [String: [String]]()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(
nibName: "GroupCell",
bundle: nil),
forCellReuseIdentifier: "groupCell")
for group in groups {
let groupKey = String(group.prefix(1))
if var groupValues = groupsDictionary[groupKey] {
groupValues.append(group)
groupsDictionary[groupKey] = groupValues
} else {
groupsDictionary[groupKey] = [group]
}
}
groupSectionTitles = [String](groupsDictionary.keys)
groupSectionTitles = groupSectionTitles.sorted(by: { $0 < $1 })
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return groupSectionTitles.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let groupKey = groupSectionTitles[section]
if let groupValues = groupsDictionary[groupKey] {
return groupValues.count
}
return 0
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return groupSectionTitles[section]
}
override func sectionIndexTitles(for tableView: UITableView) -> [String]? {
return groupSectionTitles
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath) as? GroupCell
else { return UITableViewCell() }
var currentGroup = groups[indexPath.row]
let groupKey = groupSectionTitles[indexPath.section]
if let groupValues = groupsDictionary[groupKey] {
currentGroup = groupValues[indexPath.row]
}
cell.configure(
photo: UIImage(systemName: "person.3.fill") ?? UIImage(),
name: currentGroup)
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
defer { tableView.deselectRow(
at: indexPath,
animated: true) }
performSegue(
withIdentifier: "addGroup",
sender: nil)
}
}
import UIKit
final class MyGroupsViewController: UITableViewController {
var groups = [String]() {
didSet {
//
}
}
#IBAction func addGroup(segue: UIStoryboardSegue) {
guard
segue.identifier == "addGroup",
let allGroupsController = segue.source as? AllGroupsViewController,
let groupIndexPath = allGroupsController.tableView.indexPathForSelectedRow,
!self.groups.contains(allGroupsController.groups[groupIndexPath.section])
else { return }
self.groups.append(allGroupsController.groups[groupIndexPath.section])
tableView.reloadData()
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(
nibName: "GroupCell",
bundle: nil),
forCellReuseIdentifier: "groupCell")
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
groups.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath) as? GroupCell
else { return UITableViewCell() }
let currentGroup = groups[indexPath.row]
cell.configure(
photo: UIImage(systemName: "person.3.fill") ?? UIImage(),
name: currentGroup)
return cell
}
override func tableView(
_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
groups.remove(at: indexPath.row)
tableView.deleteRows(
at: [indexPath],
with: .fade)
}
}
}
First here is what I understand the goal is:
There is a UITableViewController called MyGroupsViewController which should show all the groups the user has selected
On tapping on + from the MyGroupsViewController, the user is taken to another UITableViewController called AllGroupsViewController which shows all the groups the user can join
On selecting a UITableViewCell from AllGroupsViewController, you will unwind back to MyGroupsViewController showing the groups added for the user
I will say you were quite close and I have just added some minor things which I hope will bring you close to your goal.
One way to pass data between UIViewControllers when using segue transitions is to override a function called prepare for segue (Read more about it in the Apple docs)
1
First change I will make is to add the override prepareForSegue to the end of the MyGroupsViewController class so that I can pass the groups array to the AllGroupsViewController
You can put it anywhere, I added it below your tableview editing function
override func tableView(_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
groups.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
// This was added by me
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Check the correct segue identifier and retrieve the destination
if segue.identifier == "addGroup",
let allGroupsViewController = segue.destination as? AllGroupsViewController
{
// Set the groups variable with data we stored in the
// user groups array
allGroupsViewController.userGroups = groups
}
}
2
In AllGroupsViewController, I added an array user groups to store all the groups the user taps on and the existing user's groups will be sent to it by MyGroupsViewController in step 1
class AllGroupsViewController: UITableViewController {
var groups = [
"cats",
"birds",
"dogs",
"books",
"music",
"movies",
"art",
"science",
"tech",
"beauty",
]
// I added this. This will save groups the user wants
var userGroups: [String] = []
3.
Then I made some small changes to your tableView didSelectRowAtIndexPath function to store what the user has tapped
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
defer {
tableView.deselectRow(at: indexPath, animated: true)
}
// Added by me: Retrieve the group tapped by the user
let groupKey = groupSectionTitles[indexPath.section]
var currentGroup = ""
if let groupValues = groupsDictionary[groupKey] {
currentGroup = groupValues[indexPath.row]
}
// Added by me: Check that this group was not added for the user
// before to avoid duplicates
if userGroups.firstIndex(of: currentGroup) == nil {
userGroups.append(currentGroup)
}
self.performSegue(withIdentifier: "addGroup", sender: nil)
}
4
Finally, back in MyGroupsViewController, I updated your addGroup unwind function slightly as follows:
#IBAction func addGroup(segue: UIStoryboardSegue) {
guard segue.identifier == "addGroup",
let allGroupsViewController = segue.source as? AllGroupsViewController
else { return }
// Added by me. Removed old code and added by me. Just update
// the groups array with what was selected in AllGroupsViewController
groups = allGroupsViewController.userGroups
tableView.reloadData()
}
You will get this end result with groups added to the user, without duplicates and also you are able to add groups with the same starting letter.
You can watch the experience here on Youtube
If I missed something or did not answer something from your question, please add some more info / questions in the comments.
I am trying to create a task list with a check/uncheck when the item is selected.
I have a table view that I want the user to be able to enter items into and grow the table as a new item is required.
I can enter the data in the list and the table grows as i enter my input.
But if I need to retype an entry in the list (i.e. correcting a spelling error), when I select the cell I would like to correct I get a new cell in the table where I didn't want one and the corrected text I enter gets appended to my array instead of entering the text in the list position it was in the list.
Please help this noobie.
Here is my code:
ListTableViewControler
import UIKit
import os.log
class ListTableViewController: UITableViewController {
//MARK: Properties
var listText: [String] = [""]
var ip: IndexPath = []
var currentRow = Int()
override func viewDidLoad() {
super.viewDidLoad()
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
self.navigationItem.leftBarButtonItem = self.editButtonItem
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return listText.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Configure the cell...
let cellIdentifier = "ListTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as! ListTableViewCell
return cell
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
// test to see if the index is empty if it is empty then disable the delete button.
if editingStyle == .delete {
// Delete the row from the data source
listText.remove(at: currentRow)
tableView.deleteRows(at: [IndexPath(row: currentRow, section: 0)], with: .automatic)
tableView.reloadData()
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}else {
// disable Delete button if this is the last cell
self.editButtonItem.isEnabled = false
}
}
//MARK: Action
#IBAction func update_Table(_ sender: UITextField) {
let appendIndexPath = IndexPath(row: listText.endIndex, section: 0)
//update table
tableView.beginUpdates()
listText[listText.count - 1] = sender.text!
listText.append("")
tableView.insertRows(at: [appendIndexPath], with: .automatic)
tableView.endUpdates()
}
}
ListViewCell
class ListTableViewCell: UITableViewCell, UITextFieldDelegate {
#IBOutlet weak var checkMark: UIButton!
#IBOutlet weak var itemText: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
itemText.delegate = self
checkMark.isSelected = false
itemText.becomeFirstResponder()
}
//Mark: UITextfieldDelegate
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
//Hide Keyboard
itemText.resignFirstResponder()
return true
}
// reads the texts field data
func textFieldDidEndEditing(_ textField: UITextField) {
itemText.text = textField.text
}
//Mark: Action
#IBAction func itemChecked(_ sender: UIButton) {
// define button appearance for each state
checkMark.setImage(UIImage.checkmark, for: UIControl.State.selected)
let attributedString = NSMutableAttributedString(string: itemText.text!)
//check button doesn't response if the text field is empty
if itemText.text?.isEmpty == false {
// Change the state of the button and change the appearance of the check box
if checkMark.isSelected == false {
checkMark.isSelected = true
attributedString.addAttribute(NSAttributedString.Key.strikethroughStyle, value: 2, range: NSMakeRange(0, attributedString.length))
itemText.attributedText = attributedString
}else{
checkMark.isSelected = false
attributedString.removeAttribute(NSAttributedString.Key.strikethroughStyle, range: NSMakeRange(0, attributedString.length))
itemText.attributedText = attributedString
}
}
}
}
var searchCountry = [String]() //to set up table
var searching = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
//table
extension DictionaryViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if searching {
return searchCountry.count
} else {
return countrynameArr.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if searching {
cell.textLabel?.text = searchCountry[indexPath.row]
} else {
cell.textLabel?.text = countrynameArr[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex = indexPath.row
performSegue(withIdentifier: "segue0", sender: self)
}
}
//Searchbar
extension DictionaryViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
searchCountry = countrynameArr.filter({$0.lowercased().prefix(searchText.count) == searchText.lowercased()})
searching = true
tblView.reloadData()
}
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searching = false
searchBar.text = " "
tblView.reloadData()
}
}
Hello everyone, it is the first time I do programming so my codes are probably terrible :). I created a tableview and a searchbar. When I scroll down the tableview and click on a item I get the right cell. When I use the searchbar I will get a filtered list (that is perfect) but then..when I click on the third item of the filtered list I get the third item form the whole list. Can you please tell me my mistake?
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.