I have UITableViewController. It has data from array of files from Document Directory. I want to delete row this method self.navigationItem.leftBarButtonItem = self.editButtonItem()
but I make it, file is deleted but row is not disappeared. I tried several method but they didn't help me. I make it many once with data from CoreData. But I don't know how make it here. Please help me.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
tableView.beginUpdates()
let currentSong = listOfMP3Files[indexPath.row]
println(currentSong)
let directory = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first as! NSURL
let url = directory.URLByAppendingPathComponent(currentSong)
// println(url)
// println(indexPath)
fileManager.removeItemAtURL(url, error: nil)
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths(NSArray(object: indexPath) as [AnyObject], withRowAnimation: UITableViewRowAnimation.Automatic)
tableView.endUpdates()
} else if editingStyle == .Insert {
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case NSFetchedResultsChangeType.Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
break
case NSFetchedResultsChangeType.Update:
tableView.cellForRowAtIndexPath(indexPath!)
default: break
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case NSFetchedResultsChangeType.Insert:
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
case NSFetchedResultsChangeType.Delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
case NSFetchedResultsChangeType.Move:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Fade)
case NSFetchedResultsChangeType.Update:
break
default: break
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
The second variant
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
tableView.beginUpdates()
let currentSong = listOfMP3Files[indexPath.row]
println(currentSong)
let directory = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).first as! NSURL
let url = directory.URLByAppendingPathComponent(currentSong)
// println(url)
// println(indexPath)
fileManager.removeItemAtURL(url, error: nil)
tableView.beginUpdates()
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
tableView.endUpdates()
} else if editingStyle == .Insert {
}
}
You are missing a line to call removeAtIndex() which removes the item at specified index from datasource array
tableView.beginUpdates()
listOfMP3Files.removeAtIndex(indexPath.row) //Add this line
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
tableView.endUpdates()
Related
I've problems with my custom sectionHeaderView. I'm getting the following error:
error: Serious application error. An exception was caught from the
delegate of NSFetchedResultsController during a call to
-controllerDidChangeContent:.
Invalid update: invalid number of sections. The number of sections contained in the table view after
the update (2) must be equal to the number of sections contained in
the table view before the update (2), plus or minus the number of
sections inserted or deleted (1 inserted, 0 deleted). with userInfo
(null)
If I use the normal titleForHeaderInSection, everything's working fine.
Changing to viewForHeaderInSection causes the error. That's the code for viewForHeaderinSection:
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let currSection = fetchedResultsController.sections?[section] else { return nil }
let headerView = self.tableView.dequeueReusableHeaderFooterView(withIdentifier: ContractListHeaderView.reuseIdentifier) as! ContractListHeaderView
print(currSection.name)
headerView.sectionLabel.text = currSection.name
return headerView
}
And that's my fetchedResultsController:
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch type {
case .insert:
guard let newIndexPath = newIndexPath else { return }
tableView.insertRows(at: [newIndexPath], with: .automatic)
case .delete:
guard let indexPath = indexPath else { return }
tableView.deleteRows(at: [indexPath], with: .automatic)
case .update:
guard let indexPath = indexPath else { return }
tableView.reloadRows(at: [indexPath], with: .automatic)
case .move:
guard let indexPath = indexPath, let newIndexPath = newIndexPath else { return }
if indexPath == newIndexPath {
tableView.reloadData()
} else {
tableView.moveRow(at: indexPath, to: newIndexPath)
}
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(IndexSet(integer: sectionIndex), with: .fade)
case .delete:
tableView.deleteSections(IndexSet(integer: sectionIndex), with: .fade)
default:
break;
}
}
Thanks for the help
In my chat app, "lastMessage" is a relationship between the entities Friend and messages. Each Friend entity has a set of messages and only one lastMessage. Therefore, if i send a message to bill, bill's "lastMessage" will be updated to a newer Timestamp. That said, fetchedResultsController's fetched objects contains an array of each friend's lastMessage.timestamp . The problem I'm having is with the UITableView. If I message a friend a message in the chattingController and press back while simply having "messageTable.reloadData" inside of DidChangeContent, the friend I messaged, his lastMessage is updated to the latest timestamp and is sorted to be the first entry in the fetchedResultsController, moving him in the first spot in the tableview. However, as I wish to add animation by using self.messagesTable.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None) inside of didChangeObject, nothing is reloading properly. I tried putting it in .Insert, .Update, and .Move all at once. I'm fairly new to syncing NSFetchedResultsController to a tableView. So I'm not sure how reloading a table is normally achieved with NSFetchedResultsController.
#IBOutlet weak var messagesTable: UITableView!
lazy var fetchedResultsController: NSFetchedResultsController = {
let fetchRequest = NSFetchRequest(entityName: "Friend")
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "lastMessage.timestamp", ascending: false)]
let predicate = NSPredicate(format: "lastMessage.timestamp != nil")
fetchRequest.predicate = predicate
let context = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
frc.delegate = self
return frc
}()
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
if type == .Insert {
self.messagesTable.reloadRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.None)//reloadItemsAtIndexPaths([indexPath!])
self.messagesTable.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)//reloadItemsAtIndexPaths([indexPath!])
}
if type == .Update{
// self.collectionView?.cha([newIndexPath!])
print("h")
self.messagesTable.reloadRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.None)//reloadItemsAtIndexPaths([indexPath!])
self.messagesTable.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)//reloadItemsAtIndexPaths([indexPath!])
}
if type == .Move{
// self.collectionView?.cha([newIndexPath!])
print("h")
self.messagesTable.reloadRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.None)//reloadItemsAtIndexPaths([indexPath!])
self.messagesTable.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)//reloadItemsAtIndexPaths([indexPath!])
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.messagesTable.beginUpdates()
}
Instead of reloadRowsAtIndexPaths, use the insertRowsAtIndexPaths and deleteRowsAtIndexPaths methods. Also, use beginUpdates in the controllerWillChangeContent method, and endUpdates in controllerDidChangeContent. For example:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.messagesTable.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
case .Insert:
self.messagesTable.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
self.messagesTable.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
return
}
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
self.messagesTable.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
self.messagesTable.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
self.messagesTable.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Move:
self.messagesTable.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
self.messagesTable.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.messagesTable.endUpdates()
}
I am using UITableView with estimatedRowHeight and UITableViewAutomaticDimension. Also I am using NSFetchedResultsControllerDelegate to reflect the changes.
Everything is work fine. Now the problem is when ever I add some record to CoreData, NSFetchedResultsController called the it's delegate but an unexpected things happen. TableView suddenly scroll to Top every time.
NSFetchedResultsControllerDelegate
func controllerWillChangeContent(controller: NSFetchedResultsController) {
tableView.beginUpdates()
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
tableView.endUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .None)
break
case .Update:
tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
break
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
break
default: break;
}
}
By googling I found few answers where people suggested to use tableView: heightForRowAtIndexPath: but as my cell height is dynamic. So what should I do?
This is default behaviour of tableview, inserting an row in tableview scroll it to top.
I did faced the same issue with my app.
Here is how i able to manage the content offset of tableview, follow the steps,
1)Store the content offset of UITableview before inserting row or section.
2)Insert objects in array.
3)Reload the tableview
4)Subtract the difference and set the content offset manually.
let oldOffset: CGFloat = tblTest.contentSize.height
tblTest.reloadData()
let newOffset: CGFloat = tblTest.contentSize.height
tblTest.setContentOffset(CGPointMake(0, (newOffset - oldOffset)), animated: false)
This is how i have done in my applications so far, and with dynamic cell to overcome the reinitialise cell everytime, it is working just awesome.
Adding to #jayesh-miruliya 's answer, in your NSFetchedResultsControllerDelegate after the switch statement, put this in your code:
tableView.reloadData()
dispatch_async(dispatch_get_main_queue(), {
let topCellIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self. tableView.scrollToRowAtIndexPath(topCellIndexPath, atScrollPosition: .Top, animated: true)
})
To solve my problem I save the cell height in the willDisplay method and in estimatedHeightForRowAt I'm retrieving the value.
var cellHeights = NSMutableDictionary()
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = cellHeights.object(forKey: indexPath) {
return height as! CGFloat
}
return UITableViewAutomaticDimension
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cellHeights.setObject(cell.frame.size.height, forKey: indexPath as NSCopying)
}
tableView.reloadData()
if tableView.numberOfRowsInSection(0) > 0
{
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self. tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .None)
break
case .Update:
tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
break
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .None)
break
default: break;
}
tableView.reloadData()
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self. tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Top, animated: true)
}
}
let cou : Int = self.Array.count as Int
let lastindex : NSIndexPath = NSIndexPath(forRow: cou-1, inSection: 0)
self.TableName.scrollToRowAtIndexPath(lastindex, atScrollPosition: UITableViewScrollPosition.None, animated: true)
This code help you to scroll your tableview at particular position.
func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
tabelView.reloadData()
}
this is work for me
This code works correctly, deletes in Core Data. Table view set DELETE in red to the right but does not delete the row in the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
let manageObject: NSManagedObject = frc.objectAtIndexPath(indexPath) as! NSManagedObject
moc.deleteObject(manageObject)
do {
try moc.save()
} catch {
print("Failed to save")
return
}
}
If I do stop the App and then I run it again, the table view do not sample row deleted and sample rows that they remain.
Delete the item explicitly by adding
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
after moc.deleteObject()
or add the NSFetchedResultsControllerDelegate methods
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
self.configureCell(tableView.cellForRowAtIndexPath(indexPath!)!, atIndexPath: indexPath!)
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
self.tableView.reloadData()
}
In UITableViewController I can insert row and section at the same time with this implementations:
func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController!, didChangeObject anObject: AnyObject!, atIndexPath indexPath: NSIndexPath!, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath!) {
if controller == frc {
switch(type) {
case .Insert:
self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
case .Delete:
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
case .Update:
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
default:
break
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch(type) {
case .Insert:
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
default:
break
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.tableView.endUpdates()
}
What about UICollectionViewController? I haven't found alternative way to implement controllerWillChangeContent and controllerDidChangeContent.
collectionView works somewhat different than tableView, you need to use performBatchUpdates
var frc: NSFetchedResultsController?
var iip = [NSIndexPath]()
var dip = [NSIndexPath]()
var ins: NSIndexSet?
var des: NSIndexSet?
func controllerWillChangeContent(controller: NSFetchedResultsController) {
}
func controller(controller: NSFetchedResultsController!, didChangeObject anObject: AnyObject!, atIndexPath indexPath: NSIndexPath!, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath!) {
if controller == frc {
switch(type) {
case .Insert:
iip.append(newIndexPath)
case .Delete:
dip.append(indexPath)
case .Update:
self.collectionView!.reloadItemsAtIndexPaths([indexPath])
default:
break
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch(type) {
case .Insert:
ins = NSIndexSet(index: sectionIndex)
case .Delete:
des = NSIndexSet(index: sectionIndex)
default:
break
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.collectionView!.performBatchUpdates({
self.collectionView!.insertItemsAtIndexPaths(self.iip)
self.collectionView!.deleteItemsAtIndexPaths(self.dip)
if self.ins != nil {
self.collectionView!.insertSections(self.ins!)
}
if self.des != nil {
self.collectionView!.deleteSections(self.des!)
}
}, completion: {completed in
self.iip.removeAll(keepCapacity: false)
self.dip.removeAll(keepCapacity: false)
self.ins = nil
self.des = nil
})
}
I was able to do this by checking to see if the section of the new index path is greater than the number of sections in the collection view, and then performing batch updates.
let indexPath = IndexPath()
// index path of item to be inserted
if indexPath.section > collectionView.numberOfSections - 1 {
collectionView.performBatchUpdates({
let set = IndexSet(integer: sectionIndex)
collectionView.insertSections(set)
self.collectionView.insertItems(at: [indexPath])
}, completion: nil)
} else {
collectionView.insertItems(at: [indexPath])
}