Swift 中带有自定义 Cell 的 NSFetchedResultsController
NSFetchedResultsController with custom Cell in Swift
我想在 tableView 中有一个演示者(演讲者)列表。我正在使用 tableView 右上角的 "edit" 按钮取消隐藏或隐藏当前显示的所有对象末尾的 addSpeakerCell。这很好用并且没有问题。我可以删除对象并切换编辑按钮,一切正常。
但是当到达 addSpeakerView(通过 addSpeakerCell)并添加扬声器时,当点击保存按钮并返回到 tableView 时,会出现以下警告:
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
我的猜测是 addSpeakerCell 未使用新的 indexPath 更新。但是后来还是没找到解决办法...
如果有人能为我指出正确的解决方案,甚至为我提供必要的代码片段,我将永远感激不已
我的tableView代码:
SpeakersViewController:
class SpeakersViewController: UITableViewController
{
var managedObjectContext: NSManagedObjectContext!
lazy var fetchedResultsController: NSFetchedResultsController =
{
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Speaker", inManagedObjectContext: self.managedObjectContext)
fetchRequest.entity = entity
let sortDescriptor = NSSortDescriptor(key: "firstName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.fetchBatchSize = 20
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: self.managedObjectContext,
sectionNameKeyPath: nil,
cacheName: "Speaker")
fetchedResultsController.delegate = self
return fetchedResultsController
}()
var singleEdit = false // indicates user is swipe-deleting a particular speaker
override func viewDidLoad()
{
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = self.editButtonItem()
tableView.allowsSelectionDuringEditing = true
// CoreData Stuff
NSFetchedResultsController.deleteCacheWithName("Speaker")
performFetch()
}
func performFetch()
{
var error: NSError?
if !fetchedResultsController.performFetch(&error)
{
print("An error occurred: \(error?.localizedDescription)")
}
}
deinit
{
fetchedResultsController.delegate = nil
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
// Unhide or hide the AddSpeakerCell
override func setEditing(editing: Bool, animated: Bool)
{
super.setEditing(editing, animated: true)
if !singleEdit // if user is not swipe-deleting Speaker
{
self.navigationItem.setHidesBackButton(editing, animated: true)
}
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
let rows = sectionInfo.numberOfObjects
let indexPath = NSIndexPath(forRow: rows, inSection: 0)
let indexPaths = [indexPath]
if (editing)
{
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Top)
}
else
{
tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Top)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath.row == sectionInfo.numberOfObjects
{
return .Insert
}
else
{
return .Delete
}
}
// Unhide or hide the AddSpeakerCell
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
var rows = sectionInfo.numberOfObjects
if (editing)
{
rows++
}
return rows
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath.row < sectionInfo.numberOfObjects
{
let cell = tableView.dequeueReusableCellWithIdentifier("SpeakerCell") as SpeakerCell
let speaker = fetchedResultsController.objectAtIndexPath(indexPath) as Speaker
cell.configureForSpeaker(speaker)
return cell
}
else
{
let cell = tableView.dequeueReusableCellWithIdentifier("AddSpeakerCell") as UITableViewCell
return cell
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
if editingStyle == .Delete
{
let speaker = fetchedResultsController.objectAtIndexPath(indexPath) as Speaker
managedObjectContext.deleteObject(speaker)
var error: NSError?
if !managedObjectContext.save(&error)
{
print("An error occurred: \(error?.localizedDescription)")
}
}
}
override func tableView(tableView: UITableView, willBeginEditingRowAtIndexPath indexPath: NSIndexPath)
{
singleEdit = true
}
override func tableView(tableView: UITableView, didEndEditingRowAtIndexPath indexPath: NSIndexPath)
{
singleEdit = false
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "AddSpeaker"
{
let navigationController = segue.destinationViewController as UINavigationController
let controller = navigationController.topViewController as AddSpeakerViewController
controller.managedObjectContext = managedObjectContext
}
}
}
extension SpeakersViewController: NSFetchedResultsControllerDelegate
{
func controllerWillChangeContent(controller: NSFetchedResultsController)
{
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
{
if indexPath?.section == 0
{
switch type
{
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath?.row < sectionInfo.numberOfObjects
{
let cell = tableView.cellForRowAtIndexPath(indexPath!) as SpeakerCell
let speaker = controller.objectAtIndexPath(indexPath!) as Speaker
cell.configureForSpeaker(speaker)
}
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
{
if sectionIndex == 0
{
switch type
{
case .Insert:
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Update:
break
case .Move:
break
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController)
{
tableView.endUpdates()
}
}
AddSpeakerViewController:
class AddSpeakerViewController: UITableViewController
{
var managedObjectContext: NSManagedObjectContext!
@IBOutlet weak var speakerFirstNameTextField: UITextField!
@IBOutlet weak var speakerLastNameTextField: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
@IBAction func cancelTapped(sender: AnyObject)
{
dismissViewControllerAnimated(true, completion: nil)
}
@IBAction func saveTapped(sender: AnyObject)
{
let speaker = NSEntityDescription.insertNewObjectForEntityForName("Speaker", inManagedObjectContext: managedObjectContext) as Speaker
speaker.firstName = speakerFirstNameTextField.text
var error: NSError?
if !managedObjectContext.save(&error)
{
println("Error: \(error)")
abort()
}
dismissViewControllerAnimated(true, completion: nil)
}
}
好吧,经过几个小时的尝试和测试每一行,我终于找到了导致问题的原因。
签到
controller(didChangeObject)
对于
indexPath?.section == 0
导致问题。我真的不知道为什么......但至少它有效。
我想在 tableView 中有一个演示者(演讲者)列表。我正在使用 tableView 右上角的 "edit" 按钮取消隐藏或隐藏当前显示的所有对象末尾的 addSpeakerCell。这很好用并且没有问题。我可以删除对象并切换编辑按钮,一切正常。
但是当到达 addSpeakerView(通过 addSpeakerCell)并添加扬声器时,当点击保存按钮并返回到 tableView 时,会出现以下警告:
Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (0), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)
我的猜测是 addSpeakerCell 未使用新的 indexPath 更新。但是后来还是没找到解决办法...
如果有人能为我指出正确的解决方案,甚至为我提供必要的代码片段,我将永远感激不已
我的tableView代码:
SpeakersViewController:
class SpeakersViewController: UITableViewController
{
var managedObjectContext: NSManagedObjectContext!
lazy var fetchedResultsController: NSFetchedResultsController =
{
let fetchRequest = NSFetchRequest()
let entity = NSEntityDescription.entityForName("Speaker", inManagedObjectContext: self.managedObjectContext)
fetchRequest.entity = entity
let sortDescriptor = NSSortDescriptor(key: "firstName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
fetchRequest.fetchBatchSize = 20
let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest,
managedObjectContext: self.managedObjectContext,
sectionNameKeyPath: nil,
cacheName: "Speaker")
fetchedResultsController.delegate = self
return fetchedResultsController
}()
var singleEdit = false // indicates user is swipe-deleting a particular speaker
override func viewDidLoad()
{
super.viewDidLoad()
self.navigationItem.rightBarButtonItem = self.editButtonItem()
tableView.allowsSelectionDuringEditing = true
// CoreData Stuff
NSFetchedResultsController.deleteCacheWithName("Speaker")
performFetch()
}
func performFetch()
{
var error: NSError?
if !fetchedResultsController.performFetch(&error)
{
print("An error occurred: \(error?.localizedDescription)")
}
}
deinit
{
fetchedResultsController.delegate = nil
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
// Unhide or hide the AddSpeakerCell
override func setEditing(editing: Bool, animated: Bool)
{
super.setEditing(editing, animated: true)
if !singleEdit // if user is not swipe-deleting Speaker
{
self.navigationItem.setHidesBackButton(editing, animated: true)
}
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
let rows = sectionInfo.numberOfObjects
let indexPath = NSIndexPath(forRow: rows, inSection: 0)
let indexPaths = [indexPath]
if (editing)
{
tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: .Top)
}
else
{
tableView.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: .Top)
}
}
// MARK: - Table view data source
override func tableView(tableView: UITableView, editingStyleForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCellEditingStyle
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath.row == sectionInfo.numberOfObjects
{
return .Insert
}
else
{
return .Delete
}
}
// Unhide or hide the AddSpeakerCell
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
var rows = sectionInfo.numberOfObjects
if (editing)
{
rows++
}
return rows
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath.row < sectionInfo.numberOfObjects
{
let cell = tableView.dequeueReusableCellWithIdentifier("SpeakerCell") as SpeakerCell
let speaker = fetchedResultsController.objectAtIndexPath(indexPath) as Speaker
cell.configureForSpeaker(speaker)
return cell
}
else
{
let cell = tableView.dequeueReusableCellWithIdentifier("AddSpeakerCell") as UITableViewCell
return cell
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath)
{
if editingStyle == .Delete
{
let speaker = fetchedResultsController.objectAtIndexPath(indexPath) as Speaker
managedObjectContext.deleteObject(speaker)
var error: NSError?
if !managedObjectContext.save(&error)
{
print("An error occurred: \(error?.localizedDescription)")
}
}
}
override func tableView(tableView: UITableView, willBeginEditingRowAtIndexPath indexPath: NSIndexPath)
{
singleEdit = true
}
override func tableView(tableView: UITableView, didEndEditingRowAtIndexPath indexPath: NSIndexPath)
{
singleEdit = false
}
// MARK: - Navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)
{
if segue.identifier == "AddSpeaker"
{
let navigationController = segue.destinationViewController as UINavigationController
let controller = navigationController.topViewController as AddSpeakerViewController
controller.managedObjectContext = managedObjectContext
}
}
}
extension SpeakersViewController: NSFetchedResultsControllerDelegate
{
func controllerWillChangeContent(controller: NSFetchedResultsController)
{
tableView.beginUpdates()
}
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
{
if indexPath?.section == 0
{
switch type
{
case .Insert:
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
case .Delete:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
case .Update:
let sectionInfo = fetchedResultsController.sections![0] as NSFetchedResultsSectionInfo
if indexPath?.row < sectionInfo.numberOfObjects
{
let cell = tableView.cellForRowAtIndexPath(indexPath!) as SpeakerCell
let speaker = controller.objectAtIndexPath(indexPath!) as Speaker
cell.configureForSpeaker(speaker)
}
case .Move:
tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
}
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
{
if sectionIndex == 0
{
switch type
{
case .Insert:
tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Delete:
tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
case .Update:
break
case .Move:
break
}
}
}
func controllerDidChangeContent(controller: NSFetchedResultsController)
{
tableView.endUpdates()
}
}
AddSpeakerViewController:
class AddSpeakerViewController: UITableViewController
{
var managedObjectContext: NSManagedObjectContext!
@IBOutlet weak var speakerFirstNameTextField: UITextField!
@IBOutlet weak var speakerLastNameTextField: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
@IBAction func cancelTapped(sender: AnyObject)
{
dismissViewControllerAnimated(true, completion: nil)
}
@IBAction func saveTapped(sender: AnyObject)
{
let speaker = NSEntityDescription.insertNewObjectForEntityForName("Speaker", inManagedObjectContext: managedObjectContext) as Speaker
speaker.firstName = speakerFirstNameTextField.text
var error: NSError?
if !managedObjectContext.save(&error)
{
println("Error: \(error)")
abort()
}
dismissViewControllerAnimated(true, completion: nil)
}
}
好吧,经过几个小时的尝试和测试每一行,我终于找到了导致问题的原因。
签到
controller(didChangeObject)
对于
indexPath?.section == 0
导致问题。我真的不知道为什么......但至少它有效。