0

In my app I want to be able to remove all the rows of a particular table at once and reload the table on a completion block call.

I know how to remove a single row:

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)

           //tableView.reloadData()
            // remove the deleted item from the `UITableView`
            self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        default:
            return

        }
}

I also know that I can fetch all the rows and delete them one by one in a loop, but I'm struggling on how to add a completion block to it, so I can redraw the table after deletion is done.

Any kind of help is highly appreciated.

Eugene Gordin
  • 4,047
  • 3
  • 47
  • 80
  • you want a completion block for the fade out animation of the delete action? – luk2302 Jun 29 '15 at 19:46
  • I want the completion for reloadTable...it's not going to be called from this method above. I'll have a different method, something like deleteAll(), where I'll call remove all items from the DB for the particular table, then inside of the completion block on success I would remove all the items from the data source array and reload the table. – Eugene Gordin Jun 29 '15 at 20:00
  • @EugeneGordin if you have correctly implement the FetchedResultsController delegate methods then you don't need to call any methods to reload the table as this will be done automatically when you add or remove managed objects. So just create a deleteAll() method and then use moc.deleteObject(object) to remove all the objects - and then reload them all. Your UI should update itself to reflect these changes without any need to call reloadData. – Duncan Groenewald Jun 30 '15 at 22:56
  • If there are lots of records then you might want to do this on a background thread using a different ManagedObjectContext in which case you will need to implement some additional code to merge these updates. – Duncan Groenewald Jun 30 '15 at 22:56

3 Answers3

0

Basically all you need to do is do what you said you want to do in a completion block - just remove all the items from the datasource and update the table. The UITableView datasource delegate methods will do the rest for you and empty the tableView.

Norman G
  • 759
  • 8
  • 18
  • I'm actually asking for an example of the delete method with a completion block. If I knew how to do the completion block for core data I wouldn't ask this question :) – Eugene Gordin Jun 29 '15 at 22:58
0

Just use deleteRowsAtIndexPaths:withRowAnimation: method and apply code from a following question — How to detect that animation has ended on UITableView beginUpdates/endUpdates?

It will give you completion block functionality that you are looking, so you will be able to call reloadData in it.

For Swift it will look as following:

CATransaction.begin()
CATransaction.setCompletionBlock {
    //Reload data here
}

tableView.beginUpdates()
//Remove cells here
tableView.endUpdates()

CATransaction.commit()
Community
  • 1
  • 1
Nikita Leonov
  • 5,684
  • 31
  • 37
0

Some sample code below. The usual way would be to perform a big delete or add operation on a background thread and then use a notification to trigger the merge on the main thread. So the code below assumes the following:

  • You have a main ManagedObjectContext which is used by the
    FetchedResultsController in your TableView
  • You have a helper function to launch the delete or load methods on background threads
  • You create background managedObjectContexts and register for ContextDidSave notifications which you then use to merge the changes into the main context

Helper function for calling load or delete.

- (void)deleteDataInBackground {

            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
                [self deleteData];
            });
}

Load function

    /* Loads the required seed data */
    // Usually called on a background thread and therefor we need to process the DidSave notification
    // to merge the changed with the main context so the UI gets updated
    func loadData() {
        //FLOG(" called");

        let bgContext:NSManagedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.ConfinementConcurrencyType)

        // Register for saves in order to merge any data from background threads
        NSNotificationCenter.defaultCenter().addObserver(self, selector:"storesDidSave:", name: NSManagedObjectContextDidSaveNotification, object:bgContext)

        while (persistentStoreCoordinator == nil) {
            //FLOG(@" persistentStoreCoordinator = nil, waiting 5 seconds to try again...");
            sleep(5);
        }

        bgContext.persistentStoreCoordinator = persistentStoreCoordinator



        insertStatusCode(bgContext, number: 0, name: "Not started")
        insertStatusCode(bgContext, number: 1, name: "Started on track")
        insertStatusCode(bgContext, number: 2, name: "Behind schedule")
        insertStatusCode(bgContext, number: 3, name: "Completed")
        insertStatusCode(bgContext, number: 4, name: "Completed behind schedule")
        insertStatusCode(bgContext, number: 5, name: "On hold or cancelled")


        bgContext.processPendingChanges()

        do {

            try bgContext.save()

            //FLOG(" Seed data loaded")

        } catch {
            //FLOG("  Unresolved error \(error), \(error?.userInfo)")
        }
    } 


Code to insert new records

    func insertStatusCode(moc:NSManagedObjectContext, number:Int, name:String)
    {
        //FLOG(" called")

        if let newManagedObject:NSManagedObject = NSEntityDescription.insertNewObjectForEntityForName("StatusCode", inManagedObjectContext:moc) {

            newManagedObject.setValue(number, forKey:"number")
            newManagedObject.setValue(name, forKey:"name")

        }        
    }

Code to process the notifications and merge the changes into the main context

// NB - this may be called from a background thread so make sure we run on the main thread !!
// This is when transaction logs are loaded
func storesDidSave(notification: NSNotification!) {

    // Ignore any notifications from the main thread because we only need to merge data
    // loaded from other threads.
    if (NSThread.isMainThread()) {
        //FLOG(" main thread saved context")
        return
    }

    NSOperationQueue.mainQueue().addOperationWithBlock {
        //FLOG("storesDidSave ")
        // Set this so that after the timer goes off we perform a save
        // - without this the deletes don't appear to trigger the fetchedResultsController delegate methods !
        self.import_or_save = true

        self.createTimer() // Timer to prevent this happening too often!
        if let moc = self.managedObjectContext {
            moc.mergeChangesFromContextDidSaveNotification(notification)
        }

    }
}

And here is a Obj-C delete function, note that there are some checks to make sure the objects have not been deleted by another thread...

- (void)deleteData {
    FLOG(@"deleteData called");
    _deleteJobCount++;
    [self postJobStartedNotification];

    FLOG(@" waiting 5 seconds...");
    sleep(5);
    [self showBackgroundTaskActive];

    NSManagedObjectContext *bgContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];

    // Register for saves in order to merge any data from background threads
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(storesDidSave:) name: NSManagedObjectContextDidSaveNotification object:bgContext];


    while (self.persistentStoreCoordinator == nil) {
        FLOG(@" persistentStoreCoordinator = nil, waiting 5 seconds to try again...");
        sleep(5);
    }

    bgContext.persistentStoreCoordinator = [self persistentStoreCoordinator];

    FLOG(@" fetching data...");

    NSArray *companies = [self getData:@"Company" sortField:@"name" predicate:nil managedObjectContext:bgContext];

    NSUInteger count = companies.count;

    if (count>2) {
        for (int i = 0; i<3; i++) {
            NSManagedObject *object = [companies objectAtIndex:i];

            // Must wrap this incase another thread deleted it already
            @try {
                if ([object isDeleted]) {
                    FLOG(@" object has been deleted");
                } else {
                    FLOG(@" deleting %@", [object valueForKey:@"name"]);
                    [bgContext deleteObject:object];
                    [bgContext processPendingChanges];
                    NSError *error = nil;
                    if (![bgContext save:&error]) {
                        FLOG(@"  Unresolved error %@, %@", error, [error userInfo]);
                    }
                }
            }
            @catch (NSException *exception) {
                FLOG(@" error deleting object");
                FLOG(@"   exception is %@", exception);
            }


            FLOG(@"   waiting 5 seconds...");
            sleep(0.01);
        }

    }

    [[NSNotificationCenter defaultCenter] removeObserver:self name: NSManagedObjectContextDidSaveNotification object:bgContext];

    /*
     dispatch_async(dispatch_get_main_queue(),^(void){
     [[NSNotificationCenter defaultCenter] removeObserver:self name: NSManagedObjectContextDidSaveNotification object:nil];
     });
     */

    FLOG(@" delete ended...");
    [self showBackgroundTaskInactive];
    _deleteJobCount--;
    [self postJobDoneNotification];

}

If you have large batched take a look at the Core Data batch functions.

Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76