删除或插入核心数据后表视图未加载
Posted
技术标签:
【中文标题】删除或插入核心数据后表视图未加载【英文标题】:Table View not loading after deleting or inserting core data 【发布时间】:2017-03-19 23:36:31 【问题描述】:我正在使用核心数据将事件存储到我正在制作的议程应用程序中。我有一个段控制器,允许应用程序通过日期或标签进行排序。当我在表格视图中添加或删除事件时,视图中没有任何变化并且段栏中断,没有任何反应。但是,当我重新启动应用程序时,所有元素都已排序,段栏工作并且所有元素都在那里。它只是当我添加或删除它破坏的元素时。
代码如下:
import UIKit
import CoreData
class MainVC: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate
@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var segControl: UISegmentedControl!
var fetchedResultsController: NSFetchedResultsController<Event>!
override func viewDidLoad()
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
attemptFetchRequest()
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
attemptFetchRequest()
tableView.reloadData()
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
if let cell = tableView.dequeueReusableCell(withIdentifier: "eventCell", for: indexPath) as? EventCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
return cell
return EventCell()
//calling configure cell in this VC too
func configureCell(cell: EventCell, indexPath: NSIndexPath)
let event = fetchedResultsController.object(at: indexPath as IndexPath)
cell.configureCell(event: event)
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
if let events = fetchedResultsController.fetchedObjects , events.count > 0
let event = events[indexPath.row]
performSegue(withIdentifier: "DetailsVC", sender: event)
override func prepare(for segue: UIStoryboardSegue, sender: Any?)
if segue.identifier == "DetailsVC"
if let dest = segue.destination as? DetailsVC
if let event = sender as? Event
dest.eventToEdit = event
func numberOfSections(in tableView: UITableView) -> Int
if let sections = fetchedResultsController.sections
return sections.count
return 0
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
if let sections = fetchedResultsController.sections
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
return 0
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String?
if let sections = fetchedResultsController.sections
let currentSection = sections[section]
return currentSection.name
return nil
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat
return 126
func attemptFetchRequest()
let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
let dateSort = NSSortDescriptor(key: "date", ascending: false) //sort by date
let tagSort = NSSortDescriptor(key: "tag", ascending: false) //sort by tag
var key: String!
if segControl.selectedSegmentIndex == 0
key = "date"
fetchRequest.sortDescriptors = [dateSort]
else
key = "tag"
fetchRequest.sortDescriptors = [tagSort]
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: key, cacheName: nil)
controller.delegate = self
self.fetchedResultsController = controller
do
try fetchedResultsController.performFetch()
catch
let err = error as NSError
print("\(err)")
tableView.reloadData()
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
//tableView.beginUpdates()
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
tableView.reloadData()
//tableView.endUpdates()
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
switch(type)
case.insert:
if let indexPath = newIndexPath
tableView.insertRows(at: [indexPath], with: .fade)
break
case.delete:
if let indexPath = indexPath
tableView.deleteRows(at: [indexPath], with: .fade)
break
case.update:
if let indexPath = newIndexPath
if let cell = tableView.cellForRow(at: indexPath) as? EventCell
configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
break
case.move:
if let indexPath = indexPath
tableView.deleteRows(at: [indexPath], with: .fade)
if let indexPath = newIndexPath
tableView.insertRows(at: [indexPath], with: .fade)
break
@IBAction func segControllerChanged(_ sender: Any)
attemptFetchRequest()
tableView.reloadData()
这是我的另一个 VC
import UIKit
import CoreData
class DetailsVC: UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UITextFieldDelegate
@IBOutlet weak var image: UIImageView!
@IBOutlet weak var titleTextField: CustomTextField!
@IBOutlet weak var locationTextField: CustomTextField!
@IBOutlet weak var DescTextField: CustomTextField!
@IBOutlet weak var datePicker: UIDatePicker!
@IBOutlet weak var tagPicker: UIPickerView!
private var _tags = ["Meeting", "Breakfast/Lunch/Dinner", "Appointment", "Other"]
var tags: [String]
return _tags
var eventToEdit: Event?
var imgPicker: UIImagePickerController!
override func viewDidLoad()
super.viewDidLoad()
titleTextField.delegate = self
locationTextField.delegate = self
DescTextField.delegate = self
tagPicker.delegate = self
tagPicker.dataSource = self
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM dd, YYYY H:mm a"
if eventToEdit != nil
loadEventData()
imgPicker = UIImagePickerController()
imgPicker.delegate = self
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String?
let tag = _tags[row]
return tag
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int
return _tags.count
func numberOfComponents(in pickerView: UIPickerView) -> Int
return 1
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)
//do something
@IBAction func saveBtnPressed(_ sender: UIButton)
var event: Event! //guarenteed to have Event
let pic = Image(context: context)
pic.image = image.image
if eventToEdit == nil
event = Event(context: context)
else
event = eventToEdit
if let title = titleTextField.text
event.title = title
if let location = locationTextField.text
event.location = location
if let desc = DescTextField.text
event.detail = desc
event.toImage = pic
event.tag = _tags[tagPicker.selectedRow(inComponent: 0)]
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM dd, YYYY HH:mm"
event.fullDate = datePicker.date as NSDate?
dateFormatter.dateFormat = "MMMM dd, YYYY"
event.date = dateFormatter.string(from: datePicker.date)
dateFormatter.dateFormat = "hh:mm a"
event.time = dateFormatter.string(from: datePicker.date)
ad.saveContext()
_ = navigationController?.popViewController(animated: true)
func loadEventData()
if let event = eventToEdit
titleTextField.text = event.title
locationTextField.text = event.location
DescTextField.text = event.detail
image.image = event.toImage?.image as? UIImage
if let tag = event.tag
var i = 0
repeat
let t = _tags[i]
if t == tag
tagPicker.selectRow(i, inComponent: 0, animated: false)
i += 1
while (i < _tags.count)
@IBAction func deleteBtnPressed(_ sender: UIBarButtonItem)
if eventToEdit != nil
context.delete(eventToEdit!)
ad.saveContext()
_ = navigationController?.popViewController(animated: true)
@IBAction func imgBtnPressed(_ sender: UIButton)
present(imgPicker, animated: true, completion: nil)
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
if let img = info[UIImagePickerControllerOriginalImage] as? UIImage
image.image = img
imgPicker.dismiss(animated: true, completion: nil)
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
self.view.endEditing(true)
func textFieldShouldReturn(_ textField: UITextField) -> Bool
titleTextField.resignFirstResponder()
locationTextField.resignFirstResponder()
DescTextField.resignFirstResponder()
return true
谢谢!
【问题讨论】:
您的截止日期不是 Stack Overflow 的责任。 @matt 我的错,只是紧张.. 您说“当我向表格视图添加或删除事件时”。但我没有看到发生这种情况的任何代码。我看到您已配置为在它确实发生时做出响应,但我看不出它会发生的任何原因。为此,我需要见你,例如创建一个新的实体对象并保存上下文,这永远不会发生(事实上,你根本不会保存你的上下文)。 @JasonP 在您创建新问题之前,当您学会就您从那些花费空闲时间尝试帮助您的人那里获得的答案提供反馈时,我会考虑回答您未来的问题。 【参考方案1】:我假设您在控制台中收到The number of rows contained in an existing section after the update (0) 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 (1 inserted, 0 deleted).
的警告。在您收到错误后,tableview 将不再进行任何更新。
当您没有正确更新 tableview 以响应 fetchedResultsController 委托事件时,可能会发生这些错误。您可以通过将controllerDidChangeContent
的实现替换为tableview.reloadData() 并删除您在controllerWillChangeContent
controllerDidChange
中所做的一切来测试这是否是问题所在。不幸的是,Apple 用于从 fetchedResultsController 更新 tableview 的示例代码是错误的,并且不会处理所有情况。在此处查看我的答案:App crashes after updating CoreData model that is being displayed in a UITableView 以获取有关如何修复它的完整说明。但如果你很懒,只想让它工作,你可以简单地做reloadData
。
【讨论】:
对于“快速”修复,在我的 'controllerDidChangeContent' 中,我删除了所有内容并简单地编写了 tableview.reloadData() 并且没有实现 'controllerWillChange',但是,我的应用程序在尝试时仍然崩溃插入超过 1 个项目或删除超过 1 个项目。另外,我读了你的解决方案,我尝试在“更新”中将 indexPath 更改为 newIndexPath,但是,当我插入和删除时,我的应用程序仍然崩溃。超级困惑.. 如果删除除 reloadData 之外的所有内容都无法解决问题,那么您遇到的问题与我想的不同。控制台中是否打印了任何警告? 我编辑了我的原始帖子,您可以看到我之前对我的 controllerDidChange 和 controllerWillChange 的实现被注释掉了。当我使用 reloadData() 时,当我尝试插入、更新或删除单元格时,我得到“以 NSException 类型的未捕获异常终止”(尽管删除有时可以完美地工作,但我不确定为什么)。但是,使用我最初的 controllerDidChange 和 controllerWillChange 实现,我会得到你上面提到的错误。 尝试同时注释掉controller(_:didChange:at:for:newIndexPath:)
这似乎已经解决了崩溃问题,但你能解释一下它为什么会起作用吗?这似乎是一种非常“hacky”的修复方式,您知道如何在实现所有正确功能的同时修复它吗?顺便谢谢你编辑:现在看来,即使插入和删除工作,当我点击一个单元格时,它总是向我显示第一个单元格。以上是关于删除或插入核心数据后表视图未加载的主要内容,如果未能解决你的问题,请参考以下文章