部分中的行计数未在 TableView 中更新

Posted

技术标签:

【中文标题】部分中的行计数未在 TableView 中更新【英文标题】:Rows in Sections count not updating in TableView 【发布时间】:2016-04-18 03:17:39 【问题描述】:

当我的 UITableView 中有一个项目并且我更新确定该部分的属性值时,我遇到了困难。

我发现它与 didChangeSection 和 NSFetchedResultsChangeType.Update 选项有关。但这是我能做到的。

我不确定我需要在其中放入什么代码来更新节中的行数。

这是我得到的确切错误代码:

2016-04-17 20:00:37.126 EZ List[13722:1469523] *** 断言失败 -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/ UIKit-3512.60.7/UITableView.m:1716 2016-04-17 20:00:37.126 EZ List[13722:1469523] CoreData:错误:严重的应用程序错误。在调用 -controllerDidChangeContent: 期间,从 NSFetchedResultsController 的委托中捕获了异常。无效更新:第 0 节中的行数无效。更新后现有节中包含的行数 (2) 必须等于更新前该节中包含的行数 (3),加上或减去数字从该部分插入或删除的行数(0 插入,0 删除)加上或减去移入或移出该部分的行数(0 移入,0 移出)。与 userInfo (null)

这是我的视图控制器中的代码:

import UIKit
import CoreData

class ListItemsTableViewController: UITableViewController, NSFetchedResultsControllerDelegate 

let moc = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
var frc: NSFetchedResultsController = NSFetchedResultsController()
var list: Lists?
var catalog: Catalog?

override func viewDidLoad() 
    super.viewDidLoad()

    self.tableView.reloadData()
    self.title = list?.name

    frc = getFCR()
    frc.delegate = self

    do 
        try frc.performFetch()
     catch 
        print("Failed to perform inital fetch")
    

    // Uncomment the following line to preserve selection between presentations
    //self.clearsSelectionOnViewWillAppear = true

    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
    // self.navigationItem.rightBarButtonItem = self.editButtonItem()


override func viewWillAppear(animated: Bool) 

    let imageView = UIImageView(image: UIImage(named: "TableBackground"))
    imageView.contentMode = .ScaleAspectFill
    self.tableView.backgroundView = imageView
    self.tableView.tableFooterView = UIView(frame: CGRectZero)



override func viewDidAppear(animated: Bool) 

    frc = getFCR()
    frc.delegate = self

    do 
        try frc.performFetch()
     catch 
        fatalError("Failed to perform inital fetch")
    

    self.tableView.reloadData()



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


// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int 

    if let sections = frc.sections 
        return sections.count
    

    return 0



override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 

    if let sections = frc.sections 
        let currentSection = sections[section]
        return currentSection.numberOfObjects
    

    return 0



override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? 

    if let sections = frc.sections 
        let currentSection = sections[section]
        return currentSection.name
    

    return nil



override func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) 

    let header: UITableViewHeaderFooterView = view as! UITableViewHeaderFooterView
    header.contentView.backgroundColor = UIColor(red: 84/255, green: 200/255, blue: 214/255, alpha: 0.5)
    header.textLabel!.textColor = UIColor.whiteColor()
    //header.alpha = 0.5 //make the header transparent



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

    let cell = tableView.dequeueReusableCellWithIdentifier("listContentCell", forIndexPath: indexPath) as! ListItemsTableViewCell
    let item = frc.objectAtIndexPath(indexPath) as! Items

    cell.separatorInset = UIEdgeInsets(top: 0, left: 78, bottom: 0, right: 0)
    cell.itemName.text = item.name
    cell.itemSection.text = item.section
    cell.itemQty.text = "Qty: \(item.qty!)"
    cell.itemSize.text = item.size
    cell.itemPrice.text = floatToCurrency(Float(item.cost!))
    cell.itemImage.image = UIImage(data: item.image!)
    cell.itemID.text = String(item.id!)

    return cell



override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) 

    cell.backgroundColor = UIColor.clearColor()



override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? 

    let delete = UITableViewRowAction(style: .Destructive, title: "Delete")  (action, indexPath) in

        let request = self.fetchRequest()
        var fetchResults = [AnyObject]()

        do 
            fetchResults = try self.moc.executeFetchRequest(request)
         catch 
            fatalError("Fetching Data to Delete Failed")
        

        self.moc.deleteObject(fetchResults[indexPath.row] as! NSManagedObject)
        fetchResults.removeAtIndex(indexPath.row)

        do 
            try self.moc.save()
         catch 
            fatalError("Failed to Save after Delete")
        

    

    let edit = UITableViewRowAction(style: .Normal, title: "Edit")  (action, indexPath) in

        // Code to come

    

    let qty = UITableViewRowAction(style: .Normal, title: "Qty")  (action, indexPath) in

        // Code to come

    

    edit.backgroundColor = UIColor.init(red: 84/255, green: 200/255, blue: 214/255, alpha: 1)

    return [delete, edit, qty]



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 NSFetchedResultsChangeType.Delete:
        self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
    case NSFetchedResultsChangeType.Insert:
        self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
    case NSFetchedResultsChangeType.Update:
        let cell = self.tableView.cellForRowAtIndexPath(indexPath!) as! ListItemsTableViewCell
        let item = self.frc.objectAtIndexPath(indexPath!) as! Items

        cell.itemName.text = item.name
        cell.itemSection.text = item.section
        cell.itemQty.text = "Qty: \(item.qty!)"
        cell.itemSize.text = item.size
        cell.itemPrice.text = floatToCurrency(Float(item.cost!))
        cell.itemImage.image = UIImage(data: item.image!)
        cell.itemID.text = String(item.id!)
    default:
        print("didChangeObject Default was accessed")
        break
    



func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) 

    switch type 
    case NSFetchedResultsChangeType.Delete:
        self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
    case NSFetchedResultsChangeType.Insert:
        self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
    case NSFetchedResultsChangeType.Update:
        self.tableView.reloadData()
    default:
        print("didChangeSection Default was accessed")
        break
    



func fetchRequest() -> NSFetchRequest 

    let fetchRequest = NSFetchRequest(entityName: "Items")
    let sortDesc1 = NSSortDescriptor(key: "section", ascending: true)
    let sortDesc2 = NSSortDescriptor(key: "isChecked", ascending: true)
    let sortDesc3 = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [sortDesc1, sortDesc2, sortDesc3]

    return fetchRequest



func getFCR() -> NSFetchedResultsController 

    frc = NSFetchedResultsController(fetchRequest: fetchRequest(), managedObjectContext: moc, sectionNameKeyPath: "section" , cacheName: nil)

    return frc



func getCatalog(id: NSNumber) -> Catalog 

    var cat: Catalog?

    let fetchReq = NSFetchRequest(entityName: "Catalog")
    let pred = NSPredicate(format: "%K == %@", "id", id)
    fetchReq.predicate = pred

    do 
        let check = try moc.executeFetchRequest(fetchReq)
        cat = (check.first as! Catalog)
     catch 
        fatalError("Failed fetching Catalog Entry matching Item")
    

    return cat!


func floatToCurrency(flt: Float) -> String 

    let formatter = NSNumberFormatter()
    formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
    return String(formatter.stringFromNumber(flt)!)



/*
// 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) 
    if editingStyle == .Delete 
        // Delete the row from the data source
        tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
     else if editingStyle == .Insert 
        // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
        

*/

/*
// Override to support rearranging the table view.
override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) 


*/

/*
// Override to support conditional rearranging of the table view.
override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool 
    // Return false if you do not want the item to be re-orderable.
    return true

*/

// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) 

    var id: NSNumber

    if (segue.identifier == "listItemView") 
        let cell = sender as! UITableViewCell
        let indexPath = self.tableView.indexPathForCell(cell)

        let itemCont: ViewItemViewController = segue.destinationViewController as! ViewItemViewController
        let item: Items = self.frc.objectAtIndexPath(indexPath!) as! Items
        itemCont.item = item
        id = item.id!
        itemCont.catalog = getCatalog(id)
     else if (segue.identifier == "listItemViewEdit") 
        let cell = sender as! UITableViewCell
        let indexPath = self.tableView.indexPathForCell(cell)

        let itemCont: AddItemListViewController = segue.destinationViewController as! AddItemListViewController
        let item: Items = self.frc.objectAtIndexPath(indexPath!) as! Items
        itemCont.item = item
        id = item.id!
        itemCont.catalog = getCatalog(id)
        itemCont.list = list
    




我觉得我真的很接近正确,但我只是需要额外的推动。

【问题讨论】:

【参考方案1】:

尝试重新加载您正在谈论的更新中的行并检查是否有帮助 https://forums.developer.apple.com/thread/12184

【讨论】:

所以你认为这不是我的代码的问题,而是 Xcode 的问题? 也许是的,这就是线程所说的,尝试使用断点检查委托方法触发的次数以及更新、插入或删除哪个 NSFetchedResultsChangeType 执行。首先尝试重新加载行。跨度> 【参考方案2】:

事实证明,didChangeSection 和 NSFetchedResultsChangeType.Update 没有问题。这是 didChangeObject 和 NSFetchedResultsChangeType.Move 的问题。

以下代码解决了我的问题。其他都一样,我就放这两个函数吧。

添加 NSFetchedResultsChangeType.Move 并在其中删除旧位置和新位置。

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) 

    switch type 
    case NSFetchedResultsChangeType.Delete:
        self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
    case NSFetchedResultsChangeType.Insert:
        self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)
    /*case NSFetchedResultsChangeType.Update:
        tableView.reloadSections(NSIndexSet(index: sectionIndex), withRowAnimation: UITableViewRowAnimation.Automatic)*/
    default:
        print("didChangeSection Default was accessed")
        break
    


已移除 NSFetchedResultsChangeType.Update,因为该移动负责节更新。

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) 

    switch type 
    case NSFetchedResultsChangeType.Delete:
        self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
    case NSFetchedResultsChangeType.Insert:
        self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
    case NSFetchedResultsChangeType.Update:
        let cell = self.tableView.cellForRowAtIndexPath(indexPath!) as! ListItemsTableViewCell
        let item = self.frc.objectAtIndexPath(indexPath!) as! Items

        cell.itemName.text = item.name
        cell.itemSection.text = item.section
        cell.itemQty.text = "Qty: \(item.qty!)"
        cell.itemSize.text = item.size
        cell.itemPrice.text = floatToCurrency(Float(item.cost!))
        cell.itemImage.image = UIImage(data: item.image!)
        cell.itemID.text = String(item.id!)
    case NSFetchedResultsChangeType.Move:
        self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
        self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Automatic)
    


希望这对其他人有帮助!

【讨论】:

以上是关于部分中的行计数未在 TableView 中更新的主要内容,如果未能解决你的问题,请参考以下文章

部分错误Swift中的行数

当每个部分的行数不同时,如何获取 numberOfRows 的计数?

tableView.reloadRows(at:[indexPath],带有:.none)会像这样崩溃吗?

Flutter Firebase:Firebase 中的数据更新,但未在屏幕上自动显示计数器更新

从模型中自动更新 TableView 中的行

tableView 编辑委托方法不起作用(无效更新:第 0 节中的行数无效)