【问题标题】:UITableView crashing when deleting row with CoreData使用 CoreData 删除行时 UITableView 崩溃
【发布时间】:2015-08-20 22:50:02
【问题描述】:

这是代码:

import UIKit
import CoreData

class ExerciseViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPickerViewDataSource, UIPickerViewDelegate, NSFetchedResultsControllerDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        VDL()

        //sets stepper configs
        setsStepper.wraps = false
        setsStepper.autorepeat = true
        setsStepper.continuous = true
        setsStepper.tintColor = UIColor.redColor()
        setsStepper.minimumValue = 0
        setsStepper.maximumValue = 500
        setsStepper.value = 0

        //reps stepper configs
        repsStepper.wraps = false
        repsStepper.autorepeat = true
        repsStepper.continuous = true
        repsStepper.tintColor = UIColor.orangeColor()
        repsStepper.minimumValue = 0
        repsStepper.maximumValue = 500
        repsStepper.value = 0

        exerciseTableView.reloadData()
    }

    var moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
    var fetchedResultsController: NSFetchedResultsController?

    @IBOutlet var exerciseTableView: UITableView!

    @IBOutlet var daysPickerView: UIPickerView!

    @IBOutlet var exerciseName: UITextField!
    @IBOutlet var setsStepper: UIStepper!
    @IBOutlet var repsStepper: UIStepper!

    @IBOutlet var setsNumber: UILabel!
    @IBOutlet var repsNumber: UILabel!

    var daysArray = [TrainingDay]()
    var detailsArray = [TrainingDetails]()

    func VDL () {
        let fetchRequest = NSFetchRequest(entityName: "TrainingDay")
        let sort = NSSortDescriptor(key: "dayIndex", ascending: true)
        fetchRequest.sortDescriptors = [sort]
        daysArray = (moc!.executeFetchRequest(fetchRequest, error: nil) as? [TrainingDay])!
        if daysArray.count == 0 { // nothing there
            let dayEntity = NSEntityDescription.entityForName("TrainingDay", inManagedObjectContext: moc!)
            let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]
            for (index, name) in enumerate(days) {
                let newDay = TrainingDay(entity: dayEntity!, insertIntoManagedObjectContext: moc)
                newDay.day = name
                newDay.dayIndex = index
                daysArray.append(newDay)
                println("NAME: \(newDay.day) INDEX: \(newDay.dayIndex)")
            }
            var error: NSError?
            moc!.save(&error)
        }
    }


    func appendTrainingDetailsToArray () {
        let row = daysPickerView.selectedRowInComponent(0)
        let currentDay = daysArray[row]

        let detailsEntity = NSEntityDescription.entityForName("TrainingDetails", inManagedObjectContext: moc!)
        let trainingdetails = TrainingDetails(entity: detailsEntity!, insertIntoManagedObjectContext: moc)
        trainingdetails.exerciseName = exerciseName.text
        trainingdetails.repsNumber = repsNumber.text!
        trainingdetails.setsNumber = setsNumber.text!
        trainingdetails.trainingDay = currentDay

        var error: NSError?
        moc?.save(&error)

        if let err = error {
            var status = err.localizedFailureReason
            println("\(status)")
        } else {
            println("CURRENT SETTING: \(trainingdetails.trainingDay)")
        }
    }

    func fetchTrainingDetails() -> NSFetchRequest {

        let fetchRequest = NSFetchRequest(entityName: "TrainingDetails")
        fetchRequest.predicate = nil
        let sortDescriptor = NSSortDescriptor(key: "trainingDay", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        fetchRequest.fetchBatchSize = 20
        return fetchRequest
    }


    @IBAction func doneButton(sender: AnyObject) {
        appendTrainingDetailsToArray()
        fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchTrainingDetails(), managedObjectContext: moc!, sectionNameKeyPath: nil, cacheName: nil)
        fetchedResultsController?.delegate = self
        fetchedResultsController?.performFetch(nil)
        exerciseTableView.reloadData()
    }

    @IBAction func setsStepperAction(sender: UIStepper) {
        println("\(Int(sender.value))")
        setsNumber.text = Int(sender.value).description
    }

    @IBAction func repsStepper(sender: UIStepper) {
        println("\(Int(sender.value))")
        repsNumber.text = Int(sender.value).description
    }

    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return fetchedResultsController?.sections?[section].numberOfObjects ?? 0
    }

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("exerciseCell", forIndexPath: indexPath) as! UITableViewCell
        let details = fetchedResultsController!.objectAtIndexPath(indexPath) as! TrainingDetails
        cell.textLabel!.text = "\(details.exerciseName)"
        cell.detailTextLabel!.text = "Sets: #\(details.setsNumber) Reps: #\(details.repsNumber)"

        return cell
    }

    func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
        return true
    }

    func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle {
        return UITableViewCellEditingStyle.Delete
    }

    func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        println("section and row \(indexPath.section) \(indexPath.row) ")
        if (editingStyle == UITableViewCellEditingStyle.Delete) {
            let detailsForRow : NSManagedObject = fetchedResultsController!.objectAtIndexPath(indexPath) as! TrainingDetails
            moc?.deleteObject(detailsForRow)
            moc?.save(nil)
            exerciseTableView.beginUpdates()
            exerciseTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            exerciseTableView.endUpdates()
        }
    }

    //PICKER VIEW DELEGATE AND DATASOURCE METHODS
    func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
        return 1
    }

    func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
        return daysArray.count
    }

    func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String! {
            let trainingDay = daysArray[row]
            return trainingDay.day
    }

    func pickerView(pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {

        let currentDay = daysArray[row]
        let fetchRequest = NSFetchRequest(entityName: "TrainingDetails")
        let predicate = NSPredicate(format: "trainingDay = %@", currentDay)
        fetchRequest.predicate = predicate
        let sort = NSSortDescriptor(key: "exerciseName", ascending: true)
        fetchRequest.sortDescriptors = [sort]
        detailsArray = (moc!.executeFetchRequest(fetchRequest, error: nil) as? [TrainingDetails])!
        exerciseTableView.reloadData()

    }

    // MARK: NSFetchedResultsControllerDelegate
    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        self.exerciseTableView.beginUpdates()
    }
    func controller(controller: NSFetchedResultsController,
        didChangeObject anObject: AnyObject,
        atIndexPath indexPath: NSIndexPath?,
        forChangeType type: NSFetchedResultsChangeType,
        newIndexPath: NSIndexPath?)
    {
        switch type {
        case NSFetchedResultsChangeType.Insert:
            // Note that for Insert, we insert a row at the __newIndexPath__
            if let insertIndexPath = newIndexPath {
                self.exerciseTableView.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }
        case NSFetchedResultsChangeType.Delete:
            // Note that for Delete, we delete the row at __indexPath__
            if let deleteIndexPath = indexPath {
                self.exerciseTableView.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }
        case NSFetchedResultsChangeType.Update:
            // Note that for Update, we update the row at __indexPath__
            if let updateIndexPath = indexPath {
                let cell = self.exerciseTableView.cellForRowAtIndexPath(updateIndexPath)
                let details = self.fetchedResultsController!.objectAtIndexPath(updateIndexPath) as? TrainingDetails

                cell!.textLabel!.text = "\(details!.exerciseName)"
                cell!.detailTextLabel!.text = "Sets: #\(details!.setsNumber) Reps: #\(details!.repsNumber)"
            }
        case NSFetchedResultsChangeType.Move:
            // Note that for Move, we delete the row at __indexPath__
            if let deleteIndexPath = indexPath {
                self.exerciseTableView.deleteRowsAtIndexPaths([deleteIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }

            // Note that for Move, we insert a row at the __newIndexPath__
            if let insertIndexPath = newIndexPath {
                self.exerciseTableView.insertRowsAtIndexPaths([insertIndexPath], withRowAnimation: UITableViewRowAnimation.Fade)
            }
        }    }

    func controller(controller: NSFetchedResultsController,
        didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
        atIndex sectionIndex: Int,
        forChangeType type: NSFetchedResultsChangeType)
    {
        switch type {
        case .Insert:
            let sectionIndexSet = NSIndexSet(index: sectionIndex)
            self.exerciseTableView.insertSections(sectionIndexSet, withRowAnimation: UITableViewRowAnimation.Fade)
        case .Delete:
            let sectionIndexSet = NSIndexSet(index: sectionIndex)
            self.exerciseTableView.deleteSections(sectionIndexSet, withRowAnimation: UITableViewRowAnimation.Fade)
        default:
            ""
        }
    }
    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        exerciseTableView.endUpdates()
    }
}

每当我尝试从 tableView 中删除一行时,应用程序就会崩溃。我读到了一些关于numberOfRowInSectioncommingEditingStyle 内部被调用的内容。有人知道吗?

我尝试了许多不同的方法来擦除它,但它不会消失。

更新

好吧,我是这样设计的:

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        println("section and row \(indexPath.section) \(indexPath.row) ")
        if self.fetchedResultsController == nil {
            println("error when trying to delete object from managed object")

        } else if (editingStyle == UITableViewCellEditingStyle.Delete) {
            moc?.deleteObject(detailsArray[indexPath.row] as NSManagedObject)
            detailsArray.removeAtIndex(indexPath.row)

            var error: NSError?
            moc?.save(&error)
        }
    }

现在,当我尝试删除某些内容时,它运行正常,但前提是它是表格视图中的第一项。如果我尝试删除表格视图中间的某些内容,应用程序会因 fatal error: Array index out of range

而崩溃

另外,我如何在 Swift 中做到这一点?

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.fetchedResultsController == nil) {
    } else {
        // Do stuff
        if (editingStyle == UITableViewCellEditingStyleDelete) {
            NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
            [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

            NSError *error = nil;
            if (![context save:&error]) {
                // Replace this implementation with code to handle the error appropriately.
                // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                //abort();
            }
        }
    }
}

谢谢!!

【问题讨论】:

  • 崩溃是数组索引越界?显示异常消息。你在哪里设置节数?
  • 不要尝试从 tableView 中删除任何内容 - 使用 NSFetchedResultsController 和 NSFetchedResultsControllerDelegate 方法在 UITableView 中呈现代码数据记录。你所做的只是从 Core Data 中删除对象,UITableView 会神奇地更新自己,类似地,当你创建一个新的 Core Data 对象时,它也会神奇地出现在 UITableView 中。 developer.apple.com/library/ios/documentation/CoreData/…

标签: ios swift core-data tableview nsfetchedresultscontroller


【解决方案1】:

您的 Objective C 代码转换为 Swift

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if self.fetchedResultsController == nil {

}
else {
    if editingStyle == UITableViewCellEditingStyleDelete {
        var context: NSManagedObjectContext = self.fetchedResultsController.managedObjectContext()
        context.deleteObject(self.fetchedResultsController.objectAtIndexPath(indexPath))
        var error: NSError? = nil
        if !context.save(&error) {
            NSLog("Unresolved error %@, %@", error, error.userInfo())
        }
    }
  }
}

这里是 Link 将 Objective C 代码转换为 Swift

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    除了我上面的评论之外,还有一个使用 NSFetchedResultsController 的 UITableView 示例。

    下面的代码包括添加和删除 Core Data 对象的代码,这样 UITableView 将自动更新自身以显示/删除相关对象。

    您可以在这里http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/下载完整的示例应用程序

    //
    //  MasterViewController.m
    //  CoreDataLibraryApp
    //
    //
    
    #import "CompanyViewController.h"
    #import "OSCDStackManager.h"
    #import "CompanyDetailViewController.h"
    
    @interface CompanyViewController ()
    - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath;
    @end
    
    @implementation CompanyViewController
    
    - (void)awakeFromNib
    {
        if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
            self.clearsSelectionOnViewWillAppear = NO;
            self.preferredContentSize = CGSizeMake(320.0, 600.0);
        }
        [super awakeFromNib];
    }
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        //self.navigationItem.leftBarButtonItem = self.editButtonItem;
        self.navigationItem.leftItemsSupplementBackButton = YES;
        [self.navigationItem setHidesBackButton:NO];
        // Uncomment the following line to preserve selection between presentations.
         self.clearsSelectionOnViewWillAppear = NO;
    
        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
        // self.navigationItem.rightBarButtonItem = self.editButtonItem;
    
    
        UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(insertNewObject:)];
        self.navigationItem.rightBarButtonItem = addButton;
        //self.detailViewController = (DetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
    
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(storeChanged) name:OSStoreChangeNotification
                                                   object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshUI) name:OSDataUpdatedNotification
                                                   object:nil];
    
        UIRefreshControl *refresh = [[UIRefreshControl alloc] init];
    
        refresh.attributedTitle = [[NSAttributedString alloc] initWithString:@"Pull to Load"];
    
        [refresh addTarget:self action:@selector(refresh)
    
          forControlEvents:UIControlEventValueChanged];
    
        self.managedObjectContext = [[OSCDStackManager sharedManager] managedObjectContext];
        FLOG(@"managedObjectContext is %@",self.managedObjectContext);
        [self fetchedResultsController];
        self.detailViewController.detailItem = nil;
    }
    - (void)stopRefresh {
    
        [self.refreshControl endRefreshing];
    
    }
    - (void)refresh {
        FLOG(@"refresh called");
        [[OSCDStackManager sharedManager] loadDataInBackground];
        [self performSelector:@selector(stopRefresh) withObject:nil afterDelay:0.5];
    }
    -(void)storeChanged {
        FLOG(@"storeChanged called");
        _fetchedResultsController = nil;
        self.managedObjectContext = [[OSCDStackManager sharedManager] managedObjectContext];
        FLOG(@"managedObjectContext is %@",self.managedObjectContext);
        [self fetchedResultsController];
        self.detailViewController.detailItem = nil;
        [[self tableView] reloadData];
    }
    -(void)refreshUI {
        FLOG(@"refreshUI called");
    
        /*
         NSError *error = nil;
         if (![self.fetchedResultsController performFetch:&error]) {
         // Replace this implementation with code to handle the error appropriately.
         // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
         NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
         //abort();
         }
         */
        [[self tableView] reloadData];
    }
    - (void)didReceiveMemoryWarning
    {
        [super didReceiveMemoryWarning];
        // Dispose of any resources that can be recreated.
    }
    
    - (void)insertNewObject:(id)sender
    {
        NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
        NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
        NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
    
        NSString *str = ([[OSCDStackManager sharedManager] isCloudEnabled] ? @"New Company(c)" : @"New Company");
        // If appropriate, configure the new managed object.
        // Normally you should use accessor methods, but using KVC here avoids the need to add a custom class to the template.
        [newManagedObject setValue:str forKey:@"name"];
    
        // Save the context.
        NSError *error = nil;
        if (![context save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            //abort();
        }
    }
    
    #pragma mark - Table View
    
    // if fetchedResultsController == nil it means the database has not been initialised yet
    // and so we just return a single row saying "Loading, please wait..."
    
    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        // return 1 section of fetechedResultsController == nil
        if (self.fetchedResultsController == nil)
            return 1;
        else
            return [[self.fetchedResultsController sections] count];
    }
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        if (self.fetchedResultsController == nil) {
    
            return 1;
    
        } else {
    
            id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
            return [sectionInfo numberOfObjects];
        }
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
        [self configureCell:cell atIndexPath:indexPath];
        return cell;
    }
    
    - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (self.fetchedResultsController == nil) {
    
            return NO;
    
        } else {
            // Return NO if you do not want the specified item to be editable.
            return YES;
        }
    }
    
    - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if (self.fetchedResultsController == nil) {
        } else {
            // Do stuff
            if (editingStyle == UITableViewCellEditingStyleDelete) {
                NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
                [context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];
    
                NSError *error = nil;
                if (![context save:&error]) {
                    // Replace this implementation with code to handle the error appropriately.
                    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                    //abort();
                }
            }
        }
    }
    
    - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
    {
        // The table view should not be re-orderable.
        return NO;
    }
    - (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    
        // Don't allow user to select a row if Core Data stack is not set up yet.
        if (self.fetchedResultsController == nil) {
            NSLog(@"  fetchedResultsController == nil");
            return nil;
        }
        // By default, allow row to be selected
        return indexPath;
    }
    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        FLOG(@" called");
    
        if (self.fetchedResultsController == nil) {
            FLOG(@"  fetchedResultsController == nil");
    
        } else {
            FLOG(@"  fetchedResultsController != nil");
    
                CompanyViewController *newTableViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"CompanyMenuTableViewController"];
                newTableViewController.detailItem = [[self fetchedResultsController] objectAtIndexPath:indexPath];
                [self.navigationController pushViewController:newTableViewController animated:YES];
    
        }
    }
    - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
        FLOG(@" called");
    
        if (self.fetchedResultsController == nil) {
            FLOG(@"  fetchedResultsController == nil");
    
        } else {
            FLOG(@"  fetchedResultsController != nil");
           // if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    
    
                if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
    
                    // Create and configure a new detail view controller appropriate for the selection.
                    self.detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"CompanyDetailViewController"];
                    NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
                    self.detailViewController.detailItem = object;
                    //self.detailViewController.title = [object valueForKey:@"name"];
    
                    [self.navigationController pushViewController:self.detailViewController animated:YES];
                } else {
                    // We want to highlight the selected row
                    [self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
    
                    FLOG(@" Device is iPad");
                    DetailViewManager *detailViewManager = (DetailViewManager*)self.splitViewController.delegate;
                    FLOG(@" detailViewManager is %@", detailViewManager);
    
                    // Create and configure a new detail view controller appropriate for the selection.
                    self.detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"CompanyDetailViewController"];
                    NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
                    self.detailViewController.detailItem = object;
                    //self.detailViewController.title = [object valueForKey:@"name"];
    
                    detailViewManager.detailViewController = self.detailViewController;
                }
           // }
        }
    }
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if ([[segue identifier] isEqualToString:@"showCompanyDetail"]) {
            NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
            NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
            [[segue destinationViewController] setDetailItem:object];
        }
    }
    
    #pragma mark - Fetched results controller
    
    - (NSFetchedResultsController *)fetchedResultsController
    {
        if (_fetchedResultsController != nil) {
            return _fetchedResultsController;
        }
    
        if (self.managedObjectContext == nil) return nil;
    
        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        // Edit the entity name as appropriate.
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Company" inManagedObjectContext:self.managedObjectContext];
        [fetchRequest setEntity:entity];
    
        // Set the batch size to a suitable number.
        [fetchRequest setFetchBatchSize:20];
    
        // Edit the sort key as appropriate.
        NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
        NSArray *sortDescriptors = @[sortDescriptor];
    
        [fetchRequest setSortDescriptors:sortDescriptors];
    
        // Edit the section name key path and cache name if appropriate.
        // nil for section name key path means "no sections".
        NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
        aFetchedResultsController.delegate = self;
        self.fetchedResultsController = aFetchedResultsController;
    
        NSError *error = nil;
        if (![self.fetchedResultsController performFetch:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    
        }
    
        return _fetchedResultsController;
    }
    
    - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
    {
        NSLog(@"controllerWillChangeContent called");
        [self.tableView beginUpdates];
    }
    
    - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
               atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
    {
        switch(type) {
            case NSFetchedResultsChangeInsert:
                [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;
    
            case NSFetchedResultsChangeDelete:
                [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
    
    - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
           atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
          newIndexPath:(NSIndexPath *)newIndexPath
    {
        UITableView *tableView = self.tableView;
    
        switch(type) {
            case NSFetchedResultsChangeInsert:
                [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
    
            case NSFetchedResultsChangeDelete:
                [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
    
            case NSFetchedResultsChangeUpdate:
                [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
                break;
    
            case NSFetchedResultsChangeMove:
                [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                break;
        }
    }
    
    - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
    {
        NSLog(@"controllerDidChangeContent called");
        [self.tableView endUpdates];
    }
    
    /*
     // Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed.
    
     - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
     {
     // In the simplest, most efficient, case, reload the table view.
     [self.tableView reloadData];
     }
     */
    
    - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
    {
        if (self.fetchedResultsController == nil) {
            cell.textLabel.text = @"Loading please wait...";
            cell.accessoryType = UITableViewCellAccessoryNone;
            UIActivityIndicatorView *progressView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
            cell.accessoryView = progressView;
            [progressView startAnimating];
    
        } else {
            cell.accessoryView = nil;
            NSManagedObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
            cell.textLabel.text = [[object valueForKey:@"name"] description];
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
            cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
        }
    }
    
    @end
    

    【讨论】: