I am using Realm in swift to create a list of favorite products to be saved on the user's device.
In short, 2 views controllers (A and B) are embeded in a tab bar controller.
View controller A displays some products that are fetched from a Firebase database.
View controller A also has a "add to favorites" button that adds a product to a Realm instance.
View controller B embeds a table view to display the results from this Realm instance.
The problem is that when I delete some products from the table view, I get
the following error as soon as I go back to view controller A.
Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated
I have read that this error occurs when trying to access deleted objects from Realm, or when trying to re-add an object that was previously deleted. That doesn't seem to be the case here.
Thank you for any advice.
Here is the "add to favorite" button in my View Controller A:
#IBAction func addToFavorites(_ sender: Any) {
let realm = try! Realm()
try! realm.write {
realm.add(currentProduct)
}
}
Here is the table view code in my View Controller B:
class ViewControllerB: UIViewController, UITableViewDataSource, UITableViewDelegate {
let realm = try! Realm()
var favorites: Results<Product>!
#IBOutlet weak var favoritesTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
favoritesTableView.delegate = self
favoritesTableView.dataSource = self
favoritesTableView.rowHeight = 70
}
override func viewWillAppear(_ animated: Bool) {
favorites = realm.objects(Product.self)
favoritesTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return favorites.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! favoriteTableViewCell
let product = favorites[indexPath.row]
cell.productName.text = product.name
cell.productCategory.text = product.category
return cell
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let product = favorites[indexPath.row]
try! realm.write {
realm.delete(product)
}
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.bottom)
}
}
EDIT:
Here is the full view controller A
import UIKit
import RealmSwift
class ViewControllerA: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
var products = [Product]()
var scannedProduct = Product()
var currentProduct = Product()
let realm = try! Realm()
#IBOutlet weak var myCollectionView: UICollectionView!
#IBAction func addToFavorites(_ sender: Any) {
try! realm.write {
realm.add(currentProduct)
}
}
override func viewDidLoad() {
super.viewDidLoad()
myCollectionView.delegate = self
myCollectionView.dataSource = self
myCollectionView.isPagingEnabled = true
myCollectionView.backgroundColor = UIColor.blue
layout()
}
override func viewDidAppear(_ animated: Bool) {
currentProduct = scannedProduct
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return products.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! collectionCell
cell.productName.text = products[indexPath.item].name
cell.codeLabel.text = products[indexPath.item].code
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: myCollectionView.bounds.width, height: myCollectionView.bounds.height)
}
func layout() {
if let flowLayout = myCollectionView.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.scrollDirection = .horizontal
flowLayout.minimumLineSpacing = 0
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let currentVisibleCell = myCollectionView.visibleCells.first
let currentIndex = myCollectionView.indexPath(for: currentVisibleCell!)?.item
currentProduct = products[currentIndex!]
}
}
when trying to access deleted objects from Realm, That doesn't seem to be the case here.
Actually...
try! realm.write {
realm.delete(product)
}
tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.bottom)
These two lines shouldn't look like this. You see, Realm has this thing called a NotificationToken, and the ability to observe a results. According to the examples, it should look like this:
var notificationToken: NotificationToken?
override func viewDidLoad() {
...
// Set results notification block
self.notificationToken = results.observe { (changes: RealmCollectionChange) in
switch changes {
case .initial:
// Results are now populated and can be accessed without blocking the UI
self.tableView.reloadData()
break
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the TableView
self.tableView.beginUpdates()
self.tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
self.tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
self.tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
self.tableView.endUpdates()
break
case .error(let err): // this is probably not relevant in a non-sync scenario
break
}
}
If you have this, then you can theoretically just remove your own tableView.deleteRows(at: [indexPath], with: UITableViewRowAnimation.bottom) calls. This observer would keep the TableView up to date whenever a change occurs (write happens to Realm).
Related
I am new to Swift and I am using Swift 4.2 . I have a TableView with a label and button . When I press a button I would like to add a new row directly below the row in which the button was clicked . Right now when I click a button the new row gets added to the bottom of the TableView every time. I have been looking at posts on here but haven't been able to get it working this is my code base . I have a method called RowClick I get the indexpath of the row that was clicked but do not know how to use that to get the new row to appear directly below the clicked row .
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
InsertVideoTitle(indexPath: indexPath)
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
This is how my table looks I clicked the Facebook Rows button and it appended the string SnapChat . The Snapchat label should appear in a row below Facebook instead . Any suggestions would be great !
I think the easiest solution without re-writing this whole thing would be adding 1 to the current row of the IndexPath you captured from the action.
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
I did this from memory because I am not near an IDE, so take a look at the change and apply that change if needed in any other location.
class ExpandController: UIViewController,UITableViewDelegate,UITableViewDataSource {
#IBOutlet weak var TableSource: UITableView!
var videos: [String] = ["FaceBook","Twitter","Instagram"]
override func viewDidLoad() {
super.viewDidLoad()
TableSource.delegate = self
TableSource.dataSource = self
TableSource.tableFooterView = UIView(frame: CGRect.zero)
// Do any additional setup after loading the view.
}
#IBAction func RowClick(_ sender: UIButton) {
guard let cell = sender.superview?.superview as? ExpandTVC else {
return
}
let indexPath = TableSource.indexPath(for: cell)
var newIndexPath = indexPath;
newIndexPath.row += 1;
InsertVideoTitle(indexPath: newIndexPath);
}
func InsertVideoTitle(indexPath: IndexPath?)
{
videos.append("Snapchat")
let indexPath = IndexPath(row: videos.count - 1, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [indexPath], with: .automatic)
TableSource.endUpdates()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return videos.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let videoTitle = videos[indexPath.row]
let cell = TableSource.dequeueReusableCell(withIdentifier: "ExpandTVC") as! ExpandTVC
cell.Title.text = videoTitle
cell.ButtonRow.tag = indexPath.row
cell.ButtonRow.setTitle("Rows",for: .normal)
return cell
}
}
Your current code calls append to add the new item at the end of the array. What you want to do is insert a new row at indexPath.row+1. Array has an insert(element,at:) function.
You have to handle the case where the user has tapped the last row and not add 1 to avoid an array bounds error:
func InsertVideoTitle(indexPath: IndexPath)
{
let targetRow = indexPath.row < videos.endIndex ? indexPath.row+1 : indexPath.row
videos.insert("Snapchat" at:targetRow)
let newIndexPath = IndexPath(row: targetRow, section: 0)
TableSource.beginUpdates()
TableSource.insertRows(at: [newIndexPath], with: .automatic)
TableSource.endUpdates()
}
I have the following code which is working fine, it gets a list of items from a list in Realm called groceryList and displays them on a UITableView in descending order based on the productName. What I would like to be able to do is scroll to the latest inserted row/item in the table, right now when a new item is inserted the user may not see it since the items are alphabetically reordered and the latest item may not be visible on the tableView.
How can I scroll to the latest inserted row/item in a UITableView?
Realm Objects:
class Item:Object{
#objc dynamic var productName:String = ""
#objc dynamic var isItemActive = true
#objc dynamic var createdAt = NSDate()
}
class ItemList: Object {
#objc dynamic var listName = ""
#objc dynamic var createdAt = NSDate()
let items = List<Item>()
}
UITableView:
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate{
var allItems : Results<Item>!
var groceryList : ItemList!
override func viewDidLoad() {
super.viewDidLoad()
groceryList = realm.objects(ItemList.self).filter("listName = %#", "groceryList").first
updateResultsList()
}
func updateResultsList(){
if let list = groceryList{
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = allItems[indexPath.row]
cell.displayProductName.text = data.productName
return cell
}
}
You can use Realm notifications to know when the data source Results has been modified, then update your table view from there and do the scrolling as well.
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var allItems: Results<Item>!
var groceryList: ItemList!
var notificationToken: NotificationToken? = nil
deinit {
notificationToken?.invalidate()
}
override func viewDidLoad() {
super.viewDidLoad()
groceryList = realm.objects(ItemList.self).filter("listName = %#", "groceryList").first
updateResultsList()
observeGroceryList
}
func updateResultsList(){
if let list = groceryList {
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false)
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reusableCell", for: indexPath) as! CustomCell
let data = allItems[indexPath.row]
cell.displayProductName.text = data.productName
return cell
}
func observeGroceryList() {
notificationToken = allItems.observe { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
self?.tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Query results have changed, so apply them to the UITableView
self?.tableView.beginUpdates()
self?.tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
self?.tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
with: .automatic)
self?.tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
with: .automatic)
self?.tableView.endUpdates()
if let lastInsertedRow = insertions.last {
self?.tableView.scrollToRow(at: insertions.last, at: .none, animated: true)
}
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
print("\(error)")
}
}
}
}
Add below code as extension of tableview.
extension UITableView {
func scrollToBottom() {
let sections = numberOfSections-1
if sections >= 0 {
let rows = numberOfRows(inSection: sections)-1
if rows >= 0 {
let indexPath = IndexPath(row: rows, section: sections)
DispatchQueue.main.async { [weak self] in
self?.scrollToRow(at: indexPath, at: .bottom, animated: true)
}
}
}
}
}
Now simply use it in your method:
func updateResultsList(){
if let list = groceryList{
allItems = activeList.items.sorted(byKeyPath: "productName", ascending: false
yourTableView.scrollToBottom()
}
}
Just use this method where you want, it should be scroll down.
yourTableView.scrollToBottom()
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
}
import UIKit
import RealmSwift
class ViewController: UIViewController , UITableViewDataSource , UITableViewDelegate {
#IBOutlet weak var text1: UITextField!
#IBOutlet weak var text2: UITextField!
#IBOutlet weak var ttableview: UITableView!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array1.count
}
func ttableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array1.count
}
/////
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = ttableview.dequeueReusableCell(withIdentifier: "cell") as! Cell
cell.lable1.text = array1[indexPath.row]
cell.lable2.text = array2[indexPath.row]
return cell
}
/////
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
//this delet Realm
let cat1 = array1[indexPath.row]
let cat2 = array2[indexPath.row]
let realm = try! Realm()
try realm.write {
realm.delete(cat1)
realm.delete(cat2)
}
//this delete table view row and array
array1.remove(at: indexPath.row)
array2.remove(at: indexPath.row)
ttableview.deleteRows(at: [indexPath], with: .fade)
}
}
var array1 = [String]()
var array2 = [String]()
///////////////////
//add 2 text filed in tableview
#IBAction func Add(_ sender: Any) {
addCat()
array1.insert(text1.text!, at: 0)
array2.insert(text2.text!, at: 0)
self.ttableview.reloadData()
}
/////////////
func addCat(){
let realm = try! Realm()
let mike = CCat()
mike.name = (text1.text!)
mike.job = (text2.text!)
try! realm.write {
realm.add(mike)
//print(" \(mike.name) and \(mike.job)")
}
}
////////////////
func queryPeople(){
let realm = try! Realm()
let allPeople = realm.objects(CCat.self)
// var byname = allPeople.sorted(byProperty: "name", ascending: false)
for person in allPeople {
array1.insert(person.name, at: 0)
array2.insert(person.job, at: 0)
print("\(person.name) to \(person.job)")
ttableview.reloadData()
}
}
///////////////////
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
ttableview.delegate = self
ttableview.dataSource = self
// addCat()
queryPeople()
}
The objects you store in Realm are from CCat class (which hopefully inherits from Object), hence you can write (add) them to Realm database.
But as the Xcode says, you are trying to delete a String from realm. Which is not possible.
The swipe delete functionality is working fine, but i'm not able to add the animation, i tried everything i can but nothing worked. Whenever I add the code for animation, the app crashes when the cell is deleted. If you load the application again, you will find that the record was deleted, implying that the deletion was successful.
The crash error i'm getting is:
Invalid update: invalid number of rows in section 0. The number of
rows contained in an existing section after the update (1) must be
equal to the number of rows contained in that section before the
update (1), plus or minus the number of rows inserted or deleted from
that section (0 inserted, 1 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
The animation code blocks i tried were:
//1
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
//2
tableView.deleteRowsAtIndexPaths([indexPath],
withRowAnimation: UITableViewRowAnimation.Automatic)
I also tried to remove those codes and it didn't work too:
fetchAndSetResults()
treatmentProtocolsTableView.reloadData()
The entire code in the swift file is here, I commented out the animation blocks, and it works properly.
import UIKit
import CoreData
class Tx_Protocols: UIViewController, UITableViewDataSource, UITableViewDelegate {
//MARK: declaratoins.
weak var secureTextAlertAction: UIAlertAction?
//MARK: Core data related
var txProtocols = [TreatmentProtocolData]()
var selectedProtocol : TreatmentProtocolData? = nil
#IBOutlet weak var treatmentProtocolsTableView: UITableView!
//When button + is clicked, segue show add tx VC is initiated.
#IBAction func plusButtonAddTxProtocol(sender: AnyObject) {
self.performSegueWithIdentifier("showAddTxVC", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
fetchAndSetResults()
treatmentProtocolsTableView.delegate = self
treatmentProtocolsTableView.dataSource = self
}
//When this is used, the data shows with a slight lag, slower.
override func viewDidAppear(animated: Bool) {
fetchAndSetResults()
treatmentProtocolsTableView.reloadData()
}
//This is how you catch the app delegate core data fnxnality, GRABBING THE PROPERTY IN APP DELEGATE
func fetchAndSetResults() {
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "TreatmentProtocolData")
do {
let results = try context.executeFetchRequest(fetchRequest)
self.txProtocols = results as! [TreatmentProtocolData]
} catch let err as NSError {
print(err.debugDescription)
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier:"Cell")
cell.accessoryType = UITableViewCellAccessoryType.DisclosureIndicator
cell.textLabel!.adjustsFontSizeToFitWidth = true
cell.textLabel!.font = UIFont.boldSystemFontOfSize(17)
let treatmentProtocol = txProtocols[indexPath.row]
cell.textLabel!.text = treatmentProtocol.title
cell.imageView?.image = treatmentProtocol.getTxProtocolImage()
return cell
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return txProtocols.count
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.selectedProtocol = txProtocols[indexPath.row]
self.performSegueWithIdentifier("showTxProtocol", sender: self)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "showTxProtocol" {
let detailVC = segue.destinationViewController as! ShowTxProtocolDetailVC
detailVC.txProtocol = self.selectedProtocol
}
}
//MARK: Edittable table, delete button functionality.
func tableView(tableView: UITableView,
canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
func tableView(tableView: UITableView,
commitEditingStyle
editingStyle: UITableViewCellEditingStyle,
forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let app = UIApplication.sharedApplication().delegate as! AppDelegate
let context = app.managedObjectContext
//1
let protocolToRemove =
txProtocols[indexPath.row]
//2
context.deleteObject(protocolToRemove)
//3
do {
try context.save()
} catch let error as NSError {
print("Could not save: \(error)")
}
// //1
// tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
// //2
// tableView.deleteRowsAtIndexPaths([indexPath],
// withRowAnimation: UITableViewRowAnimation.Automatic)
fetchAndSetResults()
treatmentProtocolsTableView.reloadData()
}
}
}
I appreciate your help
You need to encapsulate inside beginUpdates() and endUpdates(). Also, update your data model that is used to load the table view:
self.tableView.beginUpdates()
self.txProtocols.removeObjectAtIndex(indexPath.row) // Check this out
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
self.tableView.endUpdates()
Based on the accepted answer by Abhinav, here was my solution (Swift 3). This implemented in my NSFetchedResultsControllerDelegate:
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
...
case .delete:
// Delete from tableView
removeFromSetsToDisplayByID(removeThisSet: myObject)
tableView.deleteRows(at: [indexPath!], with: UITableViewRowAnimation.automatic)
...
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
self.tableView.endUpdates()
}