Swift UITableView 删除行项 - tableview 数据源是 Core Data

Posted

技术标签:

【中文标题】Swift UITableView 删除行项 - tableview 数据源是 Core Data【英文标题】:Swift UITableView delete row item - tableview datasource is Core Data 【发布时间】:2016-04-25 19:07:33 【问题描述】:

我几乎完成了我的第一个 ios 应用程序,但是在从 Core Data 填充的 tableview 中删除时遇到了问题。核心数据实体有 4 个属性显示在单元格中。我尝试遵循的所有示例都使用单个属性实体。我也有两种分类描述。不确定这是否与我的问题有关。我的表格视图已正确填充.. 编辑选项可用,但是一旦选择了一行并且触摸了删除我得到一个错误,调试器停止在行 class AppDelegate: UIResponder, UIApplicationDelegate 并且 Xcode 输出以下内容

    2016-04-25 20:00:05.739 MileageLogger[41673:17841244] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3512.60.7/UITableView.m:1716
2016-04-25 20:00:05.768 MileageLogger[41673:17841244] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (14) must be equal to the number of rows contained in that section before the update (14), 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).'
*** First throw call stack:
(
    0   CoreFoundation                      0x00633494 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x02347e02 objc_exception_throw + 50
    2   CoreFoundation                      0x0063332a +[NSException raise:format:arguments:] + 138
    3   Foundation                          0x00a78322 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 118
    4   UIKit                               0x00f7b014 -[UITableView _endCellAnimationsWithContext:] + 17170
    5   UIKit                               0x00f94216 -[UITableView _updateRowsAtIndexPaths:updateAction:withRowAnimation:] + 363
    6   UIKit                               0x00f942a4 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 56
    7   UIKit                               0x0fec584a -[UITableViewAccessibility deleteRowsAtIndexPaths:withRowAnimation:] + 65
    8   MileageLogger                       0x00062789 _TFC13MileageLogger30MileageLogsTableViewController9tableViewfTCSo11UITableView18commitEditingStyleOSC27UITableViewCellEditingStyle17forRowAtIndexPathCSo11NSIndexPath_T_ + 201
    9   MileageLogger                       0x00062827 _TToFC13MileageLogger30MileageLogsTableViewController9tableViewfTCSo11UITableView18commitEditingStyleOSC27UITableViewCellEditingStyle17forRowAtIndexPathCSo11NSIndexPath_T_ + 103
    10  UIKit                               0x00fafc94 -[UITableView animateDeletionOfRowWithCell:] + 185
    11  UIKit                               0x00f80ce5 __52-[UITableView _swipeActionButtonsForRowAtIndexPath:]_block_invoke + 110
    12  UIKit                               0x00fb17f3 -[UITableView _actionButton:pushedInCell:] + 178
    13  UIKit                               0x0121cc68 -[UITableViewCell _actionButtonPushed:] + 88
    14  libobjc.A.dylib                     0x0235c0b5 -[NSObject performSelector:withObject:withObject:] + 84
    15  UIKit                               0x00e23e38 -[UIApplication sendAction:to:from:forEvent:] + 118
    16  UIKit                               0x00e23db7 -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 64
    17  UIKit                               0x00fc7f3b -[UIControl sendAction:to:forEvent:] + 79
    18  UIKit                               0x00fc82d4 -[UIControl _sendActionsForEvents:withEvent:] + 433
    19  UIKit                               0x00fc72c1 -[UIControl touchesEnded:withEvent:] + 714
    20  UIKit                               0x013aad2e _UIGestureRecognizerUpdate + 12763
    21  UIKit                               0x00ea3efd -[UIWindow _sendGesturesForEvent:] + 1559
    22  UIKit                               0x00ea55b6 -[UIWindow sendEvent:] + 1137
    23  UIKit                               0x00e46be8 -[UIApplication sendEvent:] + 266
    24  UIKit                               0x0febc369 -[UIApplicationAccessibility sendEvent:] + 72
    25  UIKit                               0x00e1b769 _UIApplicationHandleEventQueue + 7795
    26  CoreFoundation                      0x00545e5f __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 15
    27  CoreFoundation                      0x0053baeb __CFRunLoopDoSources0 + 523
    28  CoreFoundation                      0x0053af08 __CFRunLoopRun + 1032
    29  CoreFoundation                      0x0053a846 CFRunLoopRunSpecific + 470
    30  CoreFoundation                      0x0053a65b CFRunLoopRunInMode + 123
    31  GraphicsServices                    0x04b2f664 GSEventRunModal + 192
    32  GraphicsServices                    0x04b2f4a1 GSEventRun + 104
    33  UIKit                               0x00e21eb9 UIApplicationMain + 160
    34  MileageLogger                       0x00055201 main + 145
    35  libdyld.dylib                       0x02d67a25 start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

我的代码是为 TableViewController 是...

import UIKit
import CoreData

class MileageLogsTableViewController: UITableViewController, NSFetchedResultsControllerDelegate 

    @IBOutlet var milageLogTableView: UITableView!

    override func viewDidLoad() 
        super.viewDidLoad()

        do 
            try fetchedResultsController.performFetch()
         catch 
            let fetchError = error as NSError
            print("Unable to fetch MileageLog")
            print("\(fetchError), \(fetchError.localizedDescription)")
        

        // Display an Edit button in the navigation bar for this view controller.
        self.navigationItem.leftBarButtonItem = self.editButtonItem()
    

    override func didReceiveMemoryWarning() 
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    

    // MARK: - Table view data source

    private lazy var fetchedResultsController: NSFetchedResultsController = 
        // Initialize Fetch Request
        let fetchRequest = NSFetchRequest(entityName: "MileageLog")

        // Add Sort Descriptors
        let dateSort = NSSortDescriptor(key: "tripDate", ascending: false)
        let mileSort = NSSortDescriptor(key: "startMileage", ascending: false)
        fetchRequest.sortDescriptors = [dateSort, mileSort]

        let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
        let managedObjectContext = delegate.managedObjectContext

        // Initialize Fetched Results Controller
        let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: "rootCache")

        return fetchedResultsController

    ()

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int 
        if let sections = fetchedResultsController.sections 
            return sections.count
        

        return 0
    

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 
        if let sections = fetchedResultsController.sections 
            let sectionInfo = sections[section]
            return sectionInfo.numberOfObjects
        

        return 0
    


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

        let cell = tableView.dequeueReusableCellWithIdentifier("MileageLogCell") as! MileageTableViewCell

        // Fetch MileageLog
        if let mileageLog = fetchedResultsController.objectAtIndexPath(indexPath) as? MileageLog 
            //format date as medium style date
            let formatter = NSDateFormatter()
            formatter.dateStyle = .MediumStyle
            let logDateString = formatter.stringFromDate(mileageLog.tripDate!)
            //format NSNumber mileage to string
            let mileageInt:NSNumber = mileageLog.startMileage!
            let mileageString = String(mileageInt)

            cell.lb_LogDate.text = logDateString
            cell.lb_LogMileage.text = mileageString
            cell.lb_LogStartLocation.text = mileageLog.startLocation
            cell.lb_LogDestination.text = mileageLog.endLocation
        
        return cell
     


    // Override to support conditional editing of the table view.
    override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool 
        // Return false if you do not want the specified item to be editable.
        return true
    

    // Override to support editing the table view.
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) 

        switch editingStyle 

         case .Delete:
            // Delete the row from the data source
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)

        default :
            return
        

    
 

我正在努力寻找解决方案...感谢任何帮助。

【问题讨论】:

你能写 func controllerWillChangeContent(controller: NSFetchedResultsController) tableView.beginUpdates() func controllerDidChangeContent(controller: NSFetchedResultsController) tableView.endUpdates() 我认为您必须先编辑数据源以与新显示的行保持一致。也许将数据放入数组中,在调用 deleteRows 之前删除关联的索引。不幸的是,当它在 fetchedResultsController 中时,我不知道如何更改数据源 @Mych :最简单但代价高昂的修复方法就是在删除案例中调用 self.tableView.reloadData() :) 在删除行的同时,还要从您的数据源中删除该对象。因为它来自核心数据,所以使用 context.deleteObject(NSManagedObject)。如果是 NSFetchResultsController,使用 context.deleteObject(fetchedResultsController.objectAtIndexPath(indexPath) as!NSManagedObject) 我不得不开始一个新问题,因为代码已经改变,问题现在不同了......请参阅***.com/questions/36861431/… 【参考方案1】:

deleteRowsAtIndexPaths 只更新视图,不更新模型,Core Data 中也要删除对象

   ...
   case .Delete:
        // Delete the row from the data source
        let objectToDelete = fetchedResultsController.objectAtIndexPath(indexPath) as! NSManagedObject
        let context = fetchedResultsController.managedObjectContext
        context.deleteObject(objectToDelete)
        do 
          try context.save()
          tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
         catch 
           print(error)
        

当使用NSFetchedResultsController 时,强烈建议实现委托方法来处理那里的 UI 更新。

【讨论】:

vadian,感谢按照建议进行了更改以删除。我仍然收到类似的错误。由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“无效更新:第 0 节中的行数无效。更新后现有节中包含的行数 (13) 必须等于该节中包含的行数更新前的节 (13),加上或减去从该节插入或删除的行数(0 插入,1 删除),加上或减去移入或移出该节的行数(0 移入,0 移动出)。' 注意我现在只有 13 行...之前我有 14 行。所以看起来 Core Data 正在被删除,但 tableview 无法更新。 现在您需要实现NSFetchedResultsControllerDelegate 方法,以便您可以通知表格视图该操作。 正如 Marcus 已经说过的,实现结果控制器委托方法并从那里删除表视图行。 谢谢,我会看看 NSFetchedResultsControllerDelegate 方法......看看我能不能弄明白。

以上是关于Swift UITableView 删除行项 - tableview 数据源是 Core Data的主要内容,如果未能解决你的问题,请参考以下文章

Swift 3.0 无法从 UITableView 中删除一行

在 Swift 中从 UITableView 中删除一行?

如何在 Swift 中从 Core Data 中删除数据时删除 UITableView 行?

在 Swift 中删除分组 UITableView 上方的空间

Swift:UITableView - 滑动删除 - 如何让单元格内容在滑动时不移动

在 Swift 3 中基于 UITableView 中的单元格选择删除文件