How to delete a cell in tableView by clicking a button in a cell? Using coreData - ios

I create a ToDo List app.
I used tableView to list the tasks. And I use a custom class for cell. In cell contentView I have a label and one done button in it. I have successfully implemented the done button click action in my code. It works fine.
Problem
When I click the done button it deletes the last added task. But not the clicked one. And when I retry to click the done Button it perform no action. How to resolve this error
GIF added below, click link
Entity class ToDo
import Foundation
import CoreData
public class ToDo: NSManagedObject {
public override func awakeFromInsert() {
self.created = NSDate()
}
}
MainVC
import UIKit
import CoreData
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
var controller: NSFetchedResultsController<ToDo>!
#IBOutlet weak var taskTextField: CustomTextField!
#IBOutlet weak var tableView: UITableView!
var toDo: ToDo!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// generateData()
attemptFetch()
}
// to give view to cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell
}
// custom function
func configureCell(cell: ItemCell, indexPath: NSIndexPath) {
let toDo = controller.object(at: indexPath as IndexPath)
// call the method on the ItemCell
cell.configureCell(toDo: toDo)
// done button click
cell.doneBtn.tag = indexPath.row
cell.doneBtn.addTarget(self, action: #selector(MainVC.donePressed), for: UIControlEvents.touchUpInside)
}
// when select a cell
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// it ensure it have object and atleast one object in there
if let objs = controller.fetchedObjects, objs.count > 0 {
let task = objs[indexPath.row]
performSegue(withIdentifier: "ItemDetailsVC", sender: task)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ItemDetailsVC" {
if let destination = segue.destination as? ItemDetailsVC {
if let task = sender as? ToDo {
destination.taskDetails = task
}
}
}
}
// count of cells
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// we check here if any sections then take info of them and count
if let sections = controller.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
// column count
func numberOfSections(in tableView: UITableView) -> Int {
if let sections = controller.sections {
return sections.count
}
return 0
}
// give height of a cell
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 70
}
// fetching function
func attemptFetch() {
// create a fetch request with fetching Entity
let fetchRequest: NSFetchRequest<ToDo> = ToDo.fetchRequest()
// sorting area
let dateSort = NSSortDescriptor(key: "created", ascending: true)
fetchRequest.sortDescriptors = [dateSort]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
// actual fetching
do {
try controller.performFetch()
} catch {
let error = error as NSError
print("\(error)")
}
}
// when tableView changes this function starts listen for changes and
// it will handle that for you
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
// this function will listen for when we make change
// insertion, deletion .. etc
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case.insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
case.delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
break
case.update:
if let indexPath = indexPath {
let cell = tableView.cellForRow(at: indexPath)
//update the cell data
configureCell(cell: cell as! ItemCell, indexPath: indexPath as NSIndexPath)
}
break
case.move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
}
}
#IBAction func addBtnPressed(_ sender: UIButton) {
if taskTextField.text != "" && taskTextField.text != nil {
toDo = ToDo(context: context)
if let task = taskTextField.text {
toDo.title = task
}
ad.saveContext()
taskTextField.text = ""
self.tableView.reloadData()
}
}
// done button
func donePressed() {
if toDo != nil {
context.delete(toDo)
ad.saveContext()
}
}
func generateData() {
let task = ToDo(context: context)
task.title = "alwin"
let task1 = ToDo(context: context)
task1.title = "rambo"
let task2 = ToDo(context: context)
task2.title = "monisha"
let task3 = ToDo(context: context)
task3.title = "wounderlist"
let task4 = ToDo(context: context)
task4.title = "presentation"
let task5 = ToDo(context: context)
task5.title = "roundup"
// to save data
ad.saveContext()
}
}
ItemDetailsVC
import UIKit
class ItemDetailsVC: UIViewController {
var taskDetails: ToDo?
#IBOutlet weak var detailsLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// to clear the <DreamLIst to < only
if let topItem = self.navigationController?.navigationBar.topItem {
topItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)
// this is execute when tap on an existing cell
if taskDetails != nil {
loadItemData()
}
}
}
func loadItemData() {
if let task = taskDetails {
detailsLbl.text = task.title
}
}
override func viewDidLayoutSubviews() {
detailsLbl.sizeToFit()
}
#IBAction func deletePressed(_ sender: UIBarButtonItem) {
if taskDetails != nil {
context.delete(taskDetails!)
ad.saveContext()
}
_ = navigationController?.popViewController(animated: true)
}
}
storyboard, click link below
ItemCell
import UIKit
class ItemCell: UITableViewCell {
#IBOutlet weak var taskTitle: UILabel!
#IBOutlet weak var doneBtn: UIButton!
var toDo: ToDo?
func configureCell(toDo: ToDo) {
taskTitle.text = toDo.title
}
}

OK Currently you are setting the selector of your done button to outside of its container (cell) this is bad practice in general, you are configuring the cell with a ToDo but not assigning the optional inside the cell, supposedly there to keep a reference to the ToDo.
In my opinion I would change this slightly so that you store the reference to the ToDo firstly:
func configureCell(toDo: ToDo) {
self.toDo = toDo
taskTitle.text = toDo.title
}
Now on your cell create a Protocol, then configure the cell with a ToDo and a delegate, then on button press tell the delegate your button was pressed with the relevant ToDo...
protocol ToDoCellDelegate: class {
func toDoCellButtonPressed(todo: ToDo?)
}
Now on your cell configure as:
func configureCell(toDo: ToDo, delegate: ToDoCellDelegate) {
self.delegate = delegate
self.toDo = toDo
taskTitle.text = toDo.title
}
and add a ref to the delegate in the cell:
weak var delegate: ToDoCellDelegate?
now change your buttons selector to a func inside the cell
func buttonPressed() {
self.delegate?.cellToDoButtonPressed(toDo: toDo)
}
Then in your VC you conform to the delegate passing self in the configuration and implement the delegate:
extension ItemDetailsVC: ToDoCellDelegate {
func toDoCellButtonPress(toDo: ToDo?) {
if let t = toDo {
//tell context to delete todo and remove cell.
}
}
}

Ok so then you should create an IBAction outlet for your button in ItemCell and then create a protocol of this form :
protocol ItemDelegate {
func clicked()
}
class ItemCell: UITableViewViewCell {
var delegate : ItemDelegate?
var indexPath: IndexPath?
//call delegate?.clicked() where you have the gesture recogniser
}
Then in cellForRowAtIndexPath
cell.delegate = self
cell.indexPath = indexPath
Then implement the extension for your class:
extension MyTableView: ItemDelegate {
func clicked(indexPath: IndexPath) {
//dismiss cell for indexPath
}
}

Related

How do You Inherit a Custom TableViewController For An TableView in a ViewController

So I have a custom SwipeCellTableView class that I inherited from when using UITableViewControllers. Now I want to just use that class for an ib outlet table view controller in a regular View Controller. It is proving to be very difficult and seemingly not worth it anymore. Can this be done?
Here is the superclass which inherits from a TableViewController, I have tried to change it to inherit from a view controller but it just doesn't work out
class SwipeTableViewController: UITableViewController, SwipeTableViewCellDelegate {
var cell: UITableViewCell?
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = 80.0
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! SwipeTableViewCell
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> [SwipeAction]? {
guard orientation == .right else { return nil }
let deleteAction = SwipeAction(style: .destructive, title: "Delete") { action, indexPath in
// handle action by updating model with deletion
self.updateModel(at: indexPath)
}
deleteAction.image = UIImage(named: "delete-icon")
return [deleteAction]
}
func tableView(_ tableView: UITableView, editActionsOptionsForRowAt indexPath: IndexPath, for orientation: SwipeActionsOrientation) -> SwipeOptions {
var options = SwipeTableOptions()
options.expansionStyle = .destructive
//options.transitionStyle = .reveal
return options
}
func updateModel(at indexPath: IndexPath){
//update data model
print("Item deleted from super class")
}
Here is the View Controller I'm trying to access it from:
class GoalsViewController: UIViewController, SwipeTableViewController {
#IBOutlet weak var categoryTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func addCategoryPressed(_ sender: UIButton) {
performSegue(withIdentifier: "showgoalsSeg", sender: self)
}
For reference on how I was using it before when using an actual TableViewController:
class CategoryViewController: SwipeTableViewController {
var categories: Results<Category>? //optional so we can be safe
override func viewDidLoad() {
super.viewDidLoad()
loadCategory()
tableView.rowHeight = 80.0
tableView.separatorStyle = .none
}
//MARK: - Tableview Datasource Methods
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//Only get the count of categories if it's nil, else 1
return categories?.count ?? 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//fetching cell from super view
let cell = super.tableView(tableView, cellForRowAt: indexPath)
cell.textLabel?.text = categories?[indexPath.row].name ?? "No Categories Added Yet"
cell.backgroundColor = UIColor(hexString: categories?[indexPath.row].color ?? "000000")
return cell
}
//MARK: - Tableview Delegate Methods
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "goToItems", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destinationVC = segue.destination as! ToDoListViewController
if let indexPath = tableView.indexPathForSelectedRow {
destinationVC.selectedCategory = categories?[indexPath.row]
}
}
//MARK: - Add New Categories
#IBAction func addButtonPressed(_ sender: Any) {
var textField = UITextField()
let alert = UIAlertController(title: "Add New Category", message: "", preferredStyle: .alert)
let action = UIAlertAction(title: "Add Category", style: .default) { (action) in
let newCategory = Category()
newCategory.name = textField.text!
newCategory.color = UIColor.randomFlat.hexValue()
self.save(category: newCategory)
}
alert.addAction(action)
alert.addTextField { (field) in
textField = field
textField.placeholder = "Add a new category"
}
present(alert, animated: true, completion: nil)
}
func save(category: Category){
let realm = try! Realm()
do {
try realm.write{
realm.add(category)
}
} catch {
print("error saving context")
}
tableView.reloadData()
}
override func updateModel(at indexPath: IndexPath) {
super.updateModel(at: indexPath)
let realm = try! Realm()
if let categoryForDeletion = self.categories?[indexPath.row]{
do{
try realm.write{
realm.delete(categoryForDeletion)
}
} catch {
print("error deleting cell")
}
//tableView.reloadData()
}
}
func loadCategory(){
let realm = try! Realm()
categories = realm.objects(Category.self)
tableView.reloadData()
}
Is this even worth persuing? Or doable?

Appending Values Consistently in Swift

EDIT: Just changing the code to show my attempt at the Singleton suggestion.
So I am attempting to create an application which would take the selection of a user from a UITableView and pass that to another UITableView. Then, it would be appended to the array in that view and presented as the new table. The idea being that users can select from multiple lists and create one list made of all their selections.
However, I am extremely new to iOS development and while I can get it to let me take the selected value and show it in the new UITableView, it only sends the one value and does not keep or append it. Meaning I can never show multiple additions to the list.
So, what I'm getting atm is the ability to select a cell, let's say "Kevin Smith", and that value gets sent to the new UITableView and shown to the user. But if I go and select another value, "John Smith", then only John shows up and Kevin is gone.
Here is my three controllers involved:
The first UITAbleView Controller
class PlayerViewController: UITableViewController {
var resultsController: NSFetchedResultsController<Player>!
let CDSPlayer = coreDataStackPlayer()
override func viewDidLoad() {
super.viewDidLoad()
tableView.allowsMultipleSelection = true
let request: NSFetchRequest<Player> = Player.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "level", ascending: true)
request.sortDescriptors = [sortDescriptor]
resultsController = NSFetchedResultsController(
fetchRequest: request,
managedObjectContext: CDSPlayer.managedContext,
sectionNameKeyPath: nil,
cacheName: nil
)
resultsController.delegate = self
do{
try resultsController.performFetch()
} catch {
print("Perform Fetch Error: \(error)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return resultsController.sections?[section].numberOfObjects ?? 0
}
override func tableView(_ _tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCell", for: indexPath)
let selectedIndexPaths = tableView.indexPathsForSelectedRows
let rowIsSelected = selectedIndexPaths != nil && selectedIndexPaths!.contains(indexPath)
cell.accessoryType = rowIsSelected ? .checkmark : .none
let player = resultsController.object(at: indexPath)
cell.textLabel?.text = player.name
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "player2form", sender: tableView.cellForRow(at: indexPath))
}
override func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
let action = UIContextualAction(style:.destructive, title: "Delete"){(action, view, completion) in
let player = self.resultsController.object(at: indexPath)
self.resultsController.managedObjectContext.delete(player)
do {
try self.resultsController.managedObjectContext.save()
self.errorMessage(message: "Player Character Deleted.");
completion(true)
} catch {
print("Delete Failed: \(error)")
self.errorMessage(message: "Failed to Delete Player Character.");
completion(false)
}
}
action.image = UIImage(named: "trash")
action.backgroundColor = .red
return UISwipeActionsConfiguration(actions: [action])
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if let _ = sender as? UIBarButtonItem, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
}
if let cell = sender as? UITableViewCell, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
if let indexPath = tableView.indexPath(for: cell){
let player = resultsController.object(at: indexPath)
Service.shared.allPlayers.append(player)
vc.player = player
}
}
}
//Error Function
func errorMessage(message:String){
let alert = UIAlertController(title: "Alert", message: message, preferredStyle:UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.default, handler:nil);
alert.addAction(okAction);
self.present(alert, animated: true, completion:nil);
}
}
extension PlayerViewController: NSFetchedResultsControllerDelegate{
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type{
case .insert:
if let indexPath = newIndexPath{
tableView.insertRows(at: [indexPath], with: .automatic)
}
case .delete:
if let indexPath = indexPath{
tableView.deleteRows(at: [indexPath], with: .automatic)
}
case .update:
if let indexPath = indexPath, let cell = tableView.cellForRow(at: indexPath){
let player = resultsController.object(at: indexPath)
cell.textLabel?.text = player.name
}
default:
break
}
}
The Selection view that passes the data:
class AddPlayerViewController: UIViewController {
var managedContext: NSManagedObjectContext!
var player: Player?
var selectedItems = [String]()
#IBOutlet weak var done_btn: UIButton!
#IBOutlet weak var playerInput: UITextField!
#IBOutlet weak var cancel_btn: UIButton!
#IBAction func add2combat(_ sender: UIButton) {
self.performSegue(withIdentifier: "player2combat", sender: self)
}
#IBAction func done(_ sender: UIButton) {
guard let name = playerInput.text, !name.isEmpty else {
return //Add notice user cannot save empty items
}
if let player = self.player {
player.name = name
} else {
//Set values from input to the cell
let player = Player(context: managedContext)
player.name = name
}
do {
try managedContext.save()
playerInput.resignFirstResponder()
dismiss(animated: true)
} catch {
print("Error Saving Player Character: \(error)")
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "player2combat" {
let selectedItems : [String] = [playerInput.text!]
let otherVc = segue.destination as! CombatSceneViewController
otherVc.selectedItems = selectedItems
print(selectedItems)
}
}
#IBAction func cancel(_ sender: UIButton) {
playerInput.resignFirstResponder()
dismiss(animated: true)
}
override func viewDidLoad() {
super.viewDidLoad()
playerInput.becomeFirstResponder()
if let player = player {
playerInput.text = player.name
playerInput.text = player.name
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
And the UITableView that gets the data and tries to append it to the existing Array.
class CombatSceneViewController: UITableViewController{
var selectedItems = Service.shared.allSelectedItems
override func viewDidLoad() {
super.viewDidLoad()
let otherVC = AddPlayerViewController()
selectedItems.append(contentsOf: otherVC.selectedItems)
saveData()
loadData()
print(selectedItems)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return selectedItems.count
}
override func tableView(_ _tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "CombatCell", for: indexPath as IndexPath)
cell.textLabel?.text = selectedItems[indexPath.item]
return cell
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableView.reloadData()
}
func saveData() {
let data = NSMutableData()
// 1
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let path = paths[0]
let file = (path as NSString).appendingPathComponent("Persistent.plist")
//2
let archiver = NSKeyedArchiver(forWritingWith: data)
archiver.encode(selectedItems, forKey: "Agents")
archiver.finishEncoding()
data.write(toFile: file, atomically: true)
}
func loadData() {
let paths = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let path = paths[0]
let file = (path as NSString).appendingPathComponent("Persistent.plist")
// 1
if FileManager.default.fileExists(atPath: file) {
if let data = NSData(contentsOfFile: file) {
let unarchiver = NSKeyedUnarchiver(forReadingWith: data as Data)
selectedItems = unarchiver.decodeObject(forKey: "Agents") as! [String]
unarchiver.finishDecoding()
}
}
}
Singleton Class:
class Service {
static let shared = Service()
var allPlayers = [Player]()
var allSelectedItems = [String]()
}
I hope I did the formatting right... this is my first post on here, be gentle^^;.
The problem is that when you go back the old player is gone because you don't keep it , you can try to create a singleton for that
class Service {
static let shared = Service()
var allPlayers = [Player]()
var allSelectedItems = [String]()
}
// keep it here
if let cell = sender as? UITableViewCell, let vc = segue.destination as? AddPlayerViewController{
vc.managedContext = resultsController.managedObjectContext
if let indexPath = tableView.indexPath(for: cell){
let player = resultsController.object(at: indexPath)
Service.shared.allPlayers.append(player) // add this line
vc.player = player
}
}
Do same logic to append selected items and when you reach final tableView access them with
Service.shared.allPlayers
Or
Service.shared.selectedItems
That way you have a persisted container for all the selected players & items accessible anyWhere inside the app

CoreData info are not shown in the table cell

I have a custom cell and using core data the cell is not updating.
At first i created some entities at the .xcdatamodeId with a Manual/None Codegen option. The project would not build and would give me this error:
'An NSManagedObject of class 'DreamLister.Item' must have a valid NSEntityDescription.'
After i went to the generated class files for my model and deleted all the #objc(Item) lines, the project was build but nothing would come up in the cells of the simulator. The custom cell has an identifier that is used in the code for the cellForRowAt function, also i am generating test data and i access the AppDelegate like this
let ad = UIApplication.shared.delegate as! AppDelegate
let context = ad.persistentContainer.viewContext
edit: after an answer suggested to change the codegen option to Class Definition the build fails with a Swift compile error:
Command: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swiftc failed with exit code 1
here is the code of my controller
import UIKit
import CoreData
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var segment: UISegmentedControl!
var controller: NSFetchedResultsController<Item>!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
generateTestData()
attemptFetch()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell
}
func configureCell(cell: ItemCell, indexPath: NSIndexPath) {
let item = controller.object(at: indexPath as IndexPath)
cell.configureCell(item: item)
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let objs = controller.fetchedObjects , objs.count > 0 {
let item = objs[indexPath.row]
performSegue(withIdentifier: "ItemDetailsVC", sender: item)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ItemDetailsVC" {
if let destination = segue.destination as? ItemDetailsVC {
if let item = sender as? Item {
destination.itemToEdit = item
}
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = controller.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
return 0
}
func numberOfSections(in tableView: UITableView) -> Int {
return 0
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 150
}
func attemptFetch() {
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
let dateSort = NSSortDescriptor(key: "created", ascending: false)
fetchRequest.sortDescriptors = [dateSort]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
do {
try controller.performFetch()
} catch {
let error = error as NSError
print("\(error)")
}
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch(type) {
case.insert:
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
case.delete:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
break
case.update:
if let indexPath = indexPath {
let cell = tableView.cellForRow(at: indexPath) as! ItemCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
}
break
case.move:
if let indexPath = indexPath {
tableView.deleteRows(at: [indexPath], with: .fade)
}
if let indexPath = newIndexPath {
tableView.insertRows(at: [indexPath], with: .fade)
}
break
}
}
func generateTestData() {
let item = Item(context: context)
item.title = "MacBook Pro"
item.price = 1800
item.details = "I can't wait until the September event, I hope they release new MPBs"
let item2 = Item(context: context)
item2.title = "Bose Headphones"
item2.price = 300
item2.details = "But man, its so nice to be able to block out everyone with the noise canceling tech."
let item3 = Item(context: context)
item3.title = "Tesla Model S"
item3.price = 110000
item3.details = "Oh man this is a beautiful car. And one day, I willl own it"
ad.saveContext()
}
}
the code for the ItemCell
import UIKit
class ItemCell: UITableViewCell {
#IBOutlet weak var thumb: UIImageView!
#IBOutlet weak var title: UILabel!
#IBOutlet weak var price: UILabel!
#IBOutlet weak var details: UILabel!
func configureCell(item: Item) {
self.title.text = item.title
self.price.text = "$\(item.price)"
self.details.text = item.details
}
}
and the code of the core data generated files
Item+CoreDataClass.swift
import Foundation
import CoreData
public class Item: NSManagedObject {
public override func awakeFromInsert() {
super.awakeFromInsert()
self.created = NSDate()
}
}
Item+CoreDataProperties.swift
import Foundation
import CoreData
extension Item {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Item> {
return NSFetchRequest<Item>(entityName: "Item");
}
#NSManaged public var created: NSDate?
#NSManaged public var details: String?
#NSManaged public var title: String?
#NSManaged public var price: Double
#NSManaged public var toImage: Image?
#NSManaged public var toItemType: ItemType?
#NSManaged public var toStore: Store?
}
Any thoughts or ideas?
Thanks in Advance.
In core data file Select Entity and Select Codegen option to Class defination and run and see
I deleted and created the generated files for the core data again and i also change the return statements of these two functions on the MainVC from returning zero to this.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let sections = controller.sections {
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
} else {
return 0
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}

Segue not working in Swift

I'm trying to pass a certain part of an array to another view controller based on which tableview cell you click on. So when I click on the third cell for example, it'll send the array data from the third cell to the next screen. This data is an array called "todos". When I try to receive the data in the next screen, there is no data.
Code:
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EditTodo" {
print("it is")
var vc = segue.destination as! ViewController
// var indexPath = tableView.indexPathForCell(sender as UITableViewCell)
var indexPath = tableView.indexPathForSelectedRow
if let index = indexPath {
vc.todo = todos[index.row]
}
}
}
I'm not sure if it's being called at all, or what. The identifier is correct, and I'm not sure what else to do. (When I run it, the print function is not called, but I'm not even sure if it's supposed to be).
Here is the whole page of code from the 'sending data' page with the table:
import UIKit
var todos: [TodoModel] = []
var filteredTodos: [TodoModel] = []
class HomeViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UISearchDisplayDelegate {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
print("i think it worked...")
let defaults = UserDefaults.standard
if todos.count > 0 {
// Save what we have
let data = NSKeyedArchiver.archivedData(withRootObject: todos)
defaults.set(data, forKey: "TDDATA")
defaults.synchronize()
print("saved \(todos.count)")
} else if let storedTodoData = defaults.data(forKey: "TDDATA"),
let storedTodos = NSKeyedUnarchiver.unarchiveObject(with: storedTodoData) as? [TodoModel] {
// There was stored data! Use it!
todos = storedTodos
print("Used \(todos.count) stored todos")
tableView.reloadData()
self.tableView.tableFooterView = UIView()
}
//print([todos.first])
print("Here?: \(todos.first?.title)")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override var prefersStatusBarHidden: Bool {
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.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if tableView == searchDisplayController?.searchResultsTableView {
return filteredTodos.count
}
else {
return todos.count
}
}
// Display the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Must use 'self' here because searchResultsTableView needs to reuse the same cell in self.tableView
let cell = self.tableView.dequeueReusableCell(withIdentifier: "todoCell")! as UITableViewCell
var todo : TodoModel
if tableView == searchDisplayController?.searchResultsTableView {
todo = filteredTodos[indexPath.row] as TodoModel
}
else {
todo = todos[indexPath.row] as TodoModel
}
//var image = cell.viewWithTag(101) as! UIImageView
var title = cell.viewWithTag(102) as! UILabel
var date = cell.viewWithTag(103) as! UILabel
//image.image = todo.image
// image = UIImageView(image: newImage)
// if image.image == nil{
// print("nilish")
// image = UIImageView(image: UIImage(named: "EmptyProfile.png"))
// }
// image.image = todo.image
// if image.image == nil{
// print("pic is nil")
// image.image = UIImage(named: "CopyEmptyProfilePic.jpg")
// }
title.text = todo.title
date.text = "\(NSDate())"
let locale = NSLocale.current
let dateFormat = DateFormatter.dateFormat(fromTemplate: "yyyy-MM-dd", options:0, locale:locale)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = dateFormat
return cell
}
// MARK - UITableViewDelegate
// Delete the cell
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.delete {
todos.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath as IndexPath], with: UITableViewRowAnimation.automatic)
let defaults = UserDefaults.standard
if todos.count >= 0 {
// Save what we have
let data = NSKeyedArchiver.archivedData(withRootObject: todos)
defaults.set(data, forKey: "TDDATA")
defaults.synchronize()
print("saved \(todos.count)")
} else if let storedTodoData = defaults.data(forKey: "TDDATA"),
let storedTodos = NSKeyedUnarchiver.unarchiveObject(with: storedTodoData) as? [TodoModel] {
// There was stored data! Use it!
todos = storedTodos
print("Used \(todos.count) stored todos")
}
tableView.reloadData()
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 80
}
// Edit mode
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: animated)
tableView.setEditing(editing, animated: true)
}
// Move the cell
func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return self.isEditing
}
// func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
// let todo = todos.removeAtIndex(sourceIndexPath.row)
//todos.insert(todo, atIndex: destinationIndexPath.row)
// }
// MARK - UISearchDisplayDelegate
// Search the Cell
func searchDisplayController(controller: UISearchDisplayController, shouldReloadTableForSearchString searchString: String?) -> Bool {
//filteredTodos = todos.filter({( todo: TodoModel) -> Bool in
// let stringMatch = todo.title.rangeOfString(searchString)
// return stringMatch != nil
//})
// Same as below
filteredTodos = todos.filter(){$0.title.range(of: searchString!)
!= nil}
return true
}
// MARK - Storyboard stuff
// Unwind
#IBAction func close(segue: UIStoryboardSegue) {
print("closed!")
tableView.reloadData()
}
// Segue
func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "EditTodo" {
print("it is")
var vc = segue.destination as! ViewController
// var indexPath = tableView.indexPathForCell(sender as UITableViewCell)
var indexPath = tableView.indexPathForSelectedRow
if let index = indexPath {
vc.todo = todos[index.row]
}
}
}
}
How I am receiving the data in viewDidLoad of receiving screen:
nameOfDocTF.delegate = self
nameOfDocTF.text = todo?.title
outputTextView.attributedText = todo?.desc
print(todo?.title)
//prints "nil"
As I suspected you´re missing the didSelectRowAt function which will be called when you click on a cell and from there you need to call your prepareForSegue. Implement the following function and try:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.performSegue(withIdentifier: "EditTodo", sender: indexPath.row)
}
And then replace your prepareForSegue function with the following:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "EditTodo" {
let vc = segue.destination as! ViewController
if let index = sender as? Int {
vc.todo = todos[index]
}
}
}

Swift filter UITableView with out search bar

I have a UITableView that I want to filter based on a selection from slide panel view controller. This is the function that gets the returned value form the panel.
func itemSelected(type: Item) {
self.selectedItem = Item.title
delegate?.collapseSidePanels?()
}
Table view code.
var myData: Array<AnyObject> = []
var selectedItem:Array<AnyObject> = []
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellID: NSString = "Cell"
var Cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellID as String) as! UITableViewCell
var data: NSManagedObject = myData[indexPath.row] as! NSManagedObject
if tableView == selectedItem {
data = self.selectedItem[indexPath.row] as! NSManagedObject
} else
{
data = myData[indexPath.row] as! NSManagedObject
}
Cell.textLabel?.text = data.valueForKeyPath("itemname") as? String
var tt = data.valueForKeyPath("itemtype") as! String
Cell.detailTextLabel?.text = ("Item Type: \(tt)")
return Cell
}
I need to filter on the itemtype.
edit - Will not filter still so here is the full code for the tableViewController.
import UIKit
import CoreData
import Foundation
#objc
protocol tableViewControllerDelegate {
optional func toggleLeftPanel()
optional func toggleRightPanel()
optional func collapseSidePanels()
}
class tableViewController: UITableViewController, NSFetchedResultsControllerDelegate, SidePanelViewControllerDelegate {
var delegate: tableViewControllerDelegate?
var myData: Array<AnyObject> = []
var myFilteredData: Array<AnyObject> = []
#IBAction func leftTapped(sender: AnyObject) {
delegate?.toggleLeftPanel?()
}
// Use this to change table view to edit mode
// and to Change the title when clicked on.
// Make sure to have sender set as UIBarButtonItem
// or you can not change the title of the button.
var condition: Bool = true
#IBAction func buttonEdit(sender: UIBarButtonItem) {
if(condition == true) {
tableView.editing = true
sender.title = "Done"
condition = false
} else {
tableView.editing = false
sender.title = "Edit"
condition = true
}
}
let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var fetchedResultController: NSFetchedResultsController = NSFetchedResultsController()
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
// This is neeed when using panel view controller to show the bottom navbar.
self.navigationController?.setToolbarHidden(false, animated: true)
// ref app del
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
// Ref data
let context: NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "Products")
myData = context.executeFetchRequest(freq, error: nil)!
}
override func viewDidAppear(animated: Bool) {
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
if (self.myFilteredData.count != 0) {
return self.myFilteredData.count
} else {
return self.myData.count
}
}
func getFetchedResultController() -> NSFetchedResultsController {
fetchedResultController = NSFetchedResultsController(fetchRequest: NSFetchRequest(), managedObjectContext: managedObjectContext!, sectionNameKeyPath: nil, cacheName: nil)
return fetchedResultController
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellID: String = "Cell"
var cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellID as String) as! UITableViewCell
var data: NSManagedObject
if (self.myFilteredData.count != 0){
data = myFilteredData[indexPath.row] as! NSManagedObject
cell.textLabel?.text = data.valueForKeyPath("productname") as? String
var tt = data.valueForKeyPath("itemtype") as! String
cell.detailTextLabel?.text = ("Item J Type: \(tt)")
} else {
data = myData[indexPath.row] as! NSManagedObject
cell.textLabel?.text = data.valueForKeyPath("productname") as? String
var tt = data.valueForKeyPath("itemtype") as! String
cell.detailTextLabel?.text = ("Item Type: \(tt)")
}
return cell
}
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
override func tableView(tableView: UITableView, moveRowAtIndexPath sourceIndexPath: NSIndexPath, toIndexPath destinationIndexPath: NSIndexPath) {
let item: AnyObject = myData[sourceIndexPath.row]
myData.removeAtIndex(sourceIndexPath.row)
myData.insert(item, atIndex: destinationIndexPath.row)
}
// called when a row deletion action is confirmed
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
switch editingStyle {
case .Delete:
// remove the deleted item from the model
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
context.deleteObject(myData[indexPath.row] as! NSManagedObject)
myData.removeAtIndex(indexPath.row)
context.save(nil)
// remove the deleted item from the `UITableView`
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
default:
return
}
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if (segue.identifier == "showProduct"){
let selectedIndexPath:NSIndexPath = self.tableView.indexPathForSelectedRow()!
let genView:genViewController = segue.destinationViewController as! genViewController
genView.row = selectedIndexPath.row
}
else if (segue.identifier == "addProduct"){
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func itemSelected(item: Type) {
var selectedType = item.title
delegate?.collapseSidePanels?()
for (key, value) in enumerate(self.myData) {
if (value.valueForKeyPath("itemtype") !== "selectedType") {
self.myFilteredData.append(value)
dump(myFilteredData)
} else {
// do nothing with it
}
}
tableView.reloadData()
}
}
Depending on however you want the data filtered, you could loop through myData in itemSelected(), find the elements that you want in your filtered list and save them in a new array (myFilteredData).
var myFilteredData: Array<AnyObject> = []
func itemSelected(type: Item) {
self.selectedItem = Item.title
delegate?.collapseSidePanels?()
for (key, value) in enumerate(self.myData) {
if (value.valueForKeyPath("itemtype") == "yourCondition") {
self.myFilteredData.append(value)
} else {
// do nothing with it
}
}
tableView.reloadData() // use tableView.reloadSections with rowAnimation for better effect.
}
You would then reload the tableview with tableView.reloadSections(_ sections: NSIndexSet,
withRowAnimation animation: UITableViewRowAnimation), which will trigger the cellForRowAtIndexPath function. Here, you would need to decide if you want to use myData or myFilteredData for the cell's labels.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
var data:NSManagedObject
if (self.myFilteredData.count != 0) {
data = myFilteredData[indexPath.row] as! NSManagedObject
} else {
data = myData[indexPath.row] as! NSManagedObject
}
...
}
Also, don't forget to modify the numberOfRowsInSection function to return the size of the array you are populating the tableView with.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.myFilteredData.count != 0) {
return self.myFilteredData.count
} else {
return self.myData.count
}
}

Resources