如何解决Swift:UITableViewController 选择单元格并将获取的对象传递给 UIViewController
我正在尝试使用 Core Data 制作一个简单的笔记应用程序,但是我遇到了 fetchedResultsController.object(at: indexPath) 并将数据传递到我的视图控制器的问题。使用prepare(for segue: UIStoryboardSegue,sender: Any?).
我的核心数据实体名为 Notes,具有以下属性
- dateStamp 整数 64
- noteName 字符串
- noteImage 数据
- noteDescription 字符串
然而,为了理解这个问题,我做了一个单独的项目并将其限制在 noteName 和 dateStamp。
所以有3个Controllers和1个Helper文件
- 主视图控制器
- 添加视图控制器
- DetailViewController
- EditViewController
- 日期助手
MasterView 是我的 UITableController 和我的 NSFetchedResultsControllerDelegate,所以代码如下......
import UIKit
import CoreData
class MasterViewController: UITableViewController {
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
var managedobjectContext: NSManagedobjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
var fetchedResultsController: NSFetchedResultsController<Notes>!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
fetchFromCoreData()
navigationItem.leftBarButtonItem = editButtonItem
let addButton = UIBarButtonItem(barButtonSystemItem: .add,target: self,action: #selector(insertNewObject(_:)))
navigationItem.rightBarButtonItem = addButton
}
@objc
func insertNewObject(_ sender: Any) {
let addController = storyboard?.instantiateViewController(withIdentifier: "addNotes")
as! UINavigationController
self.present(addController,animated: true,completion: nil)
}
private func configureCells(cell: noterViewCell,withEvent note: Notes,indexPath: IndexPath) {
let record = fetchedResultsController.object(at: indexPath)
cell.noteName.text = record.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: record.dateStamp))
if let noteName = record.value(forKey: "noteName") as? String,let dateTime = record.value(forKey: "dateStamp") as? Int64 {
cell.noteName.text = noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: dateTime))
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
guard let sections = self.fetchedResultsController?.sections else {
fatalError("No sections in fetchedResultsController")
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "noteCell",for: indexPath) as! noterViewCell
guard let object = self.fetchedResultsController?.object(at: indexPath) else {
fatalError("Attempt to configure cell without a managed object")
}
configureCells(cell: cell,withEvent: object,indexPath: indexPath)
cell.selectbutton.addTarget(self,action: #selector(selectNote(_:)),for: .touchUpInside)
return cell
}
override func tableView(_ tableView: UITableView,canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView,commit editingStyle: UITableViewCell.EditingStyle,forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let context = fetchedResultsController.managedobjectContext
context.delete(fetchedResultsController.object(at: indexPath))
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror),\(nserror.userInfo)")
}
}
}
func fetchFromCoreData() {
let fetchRequest: NSFetchRequest<Notes> = Notes.fetchRequest()
fetchRequest.fetchBatchSize = 800
let creationDateSortDescriptor = NSSortDescriptor(key: "dateStamp",ascending: false)
//let nameSortDescriptor = NSSortDescriptor(key: "noteName",ascending: false)
fetchRequest.sortDescriptors = [creationDateSortDescriptor]
let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,managedobjectContext: self.managedobjectContext!,sectionNameKeyPath: "dateStamp",cacheName: nil)
aFetchedResultsController.delegate = self
fetchedResultsController = aFetchedResultsController
do {
try fetchedResultsController.performFetch()
self.tableView.reloadData()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() 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.
let nserror = error as NSError
fatalError("Unresolved error \(nserror),\(nserror.localizedDescription),\(nserror.localizedFailureReason ?? "Could not retrieve")")
//print("Could not save note to CoreData: \(error.localizedDescription)")
}
}
}
extension MasterViewController: NSFetchedResultsControllerDelegate {
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.beginUpdates()
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,didChange sectionInfo: NSFetchedResultsSectionInfo,atSectionIndex sectionIndex: Int,for type: NSFetchedResultsChangeType) {
switch type {
case .insert:
tableView.insertSections(IndexSet(integer: sectionIndex),with: .fade)
case .delete:
tableView.deleteSections(IndexSet(integer: sectionIndex),with: .fade)
default:
return
}
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,didChange anObject: Any,at indexPath: IndexPath?,for type: NSFetchedResultsChangeType,newIndexPath: IndexPath?) {
switch type {
case .insert:
tableView.insertRows(at: [newIndexPath!],with: .fade)
case .delete:
tableView.deleteRows(at: [indexPath!],with: .fade)
case .update:
tableView.reloadRows(at: [newIndexPath!],with: .fade)
if let updateIndexPath = newIndexPath {
let cell = self.tableView.cellForRow(at: updateIndexPath) as! noterViewCell
let event = anObject as! Notes
cell.dateLabel.text = event.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: event.dateStamp))
}
case .move:
configureCells(cell: tableView.cellForRow(at: indexPath!) as! noterViewCell,withEvent: anObject as! Notes,indexPath: indexPath!)
tableView.moveRow(at: indexPath!,to: newIndexPath!)
@unkNown default:
fatalError("Unresolved error")
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
}
}
我的 AddViewController
import UIKit
import CoreData
class AddViewController: UIViewController {
@IBOutlet var noteField: UITextField!
@IBOutlet var dateStamp: UILabel!
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
let masterView = MasterViewController()
var isExisting: Bool = false
var note:Notes? = nil
var managedobjectContext: NSManagedobjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
override func viewDidLoad() {
super.viewDidLoad()
configureView()
}
var detailItem: Notes? {
didSet {
// Update the view.
configureView()
}
}
func configureView() {
if let detail = detailItem {
if let noteFieldLabel = noteField,let dateTime = dateStamp {
noteFieldLabel.text = detail.noteName
dateTime.text = dateHelper.convertDate(date: Date.init(seconds: detail.dateStamp))
}
}
}
@IBAction func saveImageButtonpressed(_ sender: Any) {
let context = self.managedobjectContext
let newEvent = Notes(context: context!)
newEvent.noteName = noteField.text
newEvent.dateStamp = noteCreationTimeStamp
do {
try context?.save()
} catch {
let error = error as NSError
fatalError("Unresolved error \(error),\(error.localizedDescription)")
}
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true,completion: nil)
}
else {
self.navigationController!.pushViewController(masterView,animated: true)
}
}
@IBAction func cancelView(_ sender: AnyObject) {
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true,completion: nil)
}
}
}
一切都很好,我的 saveImageButtonpressed 将数据保存到我的 managedobjectContext 并毫无问题地显示在 UITableview 上。
此代码用于我的 DetailViewController
import UIKit
import CoreData
class DetailViewController: UIViewController {
@IBOutlet var dateLabel: UILabel!
@IBOutlet var noteField: UILabel!
let masterView = MasterViewController()
var notes: Notes?
var myNotes = [Notes]()
var isExisting: Bool = false
var index = IndexPath()
private let noteCreationTimeStamp : Int64 = Date().timetoSeconds()
var managedobjectContext: NSManagedobjectContext? {
return (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
}
var detailItem: Notes? {
didSet {
// Update the view.
configureView()
}
}
func configureView() {
if let detail = detailItem {
if let noteFieldLabel = noteField,let dateStamp = dateLabel {
noteFieldLabel.text = detail.noteName
dateStamp.text = dateHelper.convertDate(date: Date.init(seconds: detail.dateStamp))
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//fetchNote()
configureView()
}
@IBAction func cancelView(_ sender: AnyObject) {
let isPresentingMode = self.presentingViewController is UINavigationController
if isPresentingMode {
self.dismiss(animated: true,completion: nil)
}
}
//
func fetchNote() {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Notes")
do {
myNotes = try managedobjectContext?.fetch(fetchRequest) as! [Notes]
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror),\(nserror.localizedDescription,\(nserror.localizedFailureReason ?? "Could not retrieve")")
}
}
//
}
所以我试图选择我的单元格以将保存的数据传递给我的 DetailViewController 但它没有选择。什么都没发生。我的 DetailViewController 中的 fetchNote 函数只是尝试的附加组件。
这是我的准备代码(对于segue:UIStoryboardSegue,sender:Any?)
override func prepare(for segue: UIStoryboardSegue,sender: Any?) {
if segue.identifier == "showDetails" {
if let selectedindexPath = tableView.indexPathForSelectedRow {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
let objects = fetchedResultsController.object(at: selectedindexPath)
controller.detailItem = objects
}
}
}
和我的 tableView.didSelectRowAt: indexPath
override func tableView(_ tableView: UITableView,didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath,animated: true)
let cell = self.tableView.cellForRow(at: indexPath) as! noterViewCell
let event = fetchedResultsController.object(at: indexPath)
cell.dateLabel.text = event.noteName
cell.dateLabel.text = dateHelper.convertDate(date: Date.init(seconds: event.dateStamp))
//configureCells(cell: tableView.cellForRow(at: indexPath!) as! noterViewCell,indexPath: indexPath!)
self.performSegue(withIdentifier: "showDetails",sender: self)
}
我添加了 self.performSegue(withIdentifier: "showDetails",sender: self) 但 DetailViewController 是空白的。
为了进一步理解,我在 cellForRowAt indexPath 中添加了按钮:...
cell.selectbutton.addTarget(self,for: .touchUpInside)
使用以下操作
@objc
func selectNote(_ sender: noterViewCell) {
let indexPath = IndexPath()
let objects = fetchedResultsController.object(at: indexPath) as Notes
let controller = DetailViewController()
controller.detailItem = objects
//let detailView = //storyboard?.instantiateViewController(withIdentifier: "viewNotes") as! //UINavigationController
//self.present(detailView,completion: nil)
let detailViews = DetailViewController()
self.navigationController?.popToViewController(detailViews,animated: true)
}
这引发了“NSinvalidargumentexception”,原因:“索引 9223372036854775807 处没有节”,两种方法都尝试访问我的详细信息视图
在我的按钮操作 @objc MasterViewController_selectNote(_:) 中,这完全是针对 fetchedResultsController.object 的
线程中还列出了 [NSFetchedResultsController objectAtIndexPath:]:向下滚动我发现了另一个原因,“无法在 -performFetch 之前访问获取的对象:”以及“NSFetchedResultsController:在索引 %lu 的部分中索引 %lu 处没有对象"
我在 viewDidLoad() 中调用了我的 fetchFromCoreData() ...我取出了 performFetch 我直接将它粘贴到 viewDidLoad() 中。如果我单击按钮,仍然会抛出相同的错误。再次使用 prepareForSegue 选择空白视图。我再次尝试在我的 fetchFromCoreData() 函数中使用返回值。
我不确定我是否通过添加按钮来解决问题,但在所有情况下,我都试图解决问题 prepareForSegue 它仍然传递一个空白视图。
我也尝试使用此函数验证 indexPath
func validateIndexPath(_ indexPath: IndexPath) -> Bool {
if let sections = self.fetchedResultsController.sections,indexPath.section < sections.count {
if indexPath.row < sections[indexPath.section].numberOfObjects {
return true
}
}
return false
}
再次无济于事,在 cellForRowAt indexPath 上调用它,应用程序仍然以相同的“NSinvalidargumentexception”和 prepareForSegue 传递空白视图终止。
然后我在我的 tableView 中添加了我的 titleForHeaderInSection,它是按月份和年份设置的,当我将数据保存在我的 AddViewController 中时显示正常。我希望它可能会有所作为,但是......
func monthDate(string:String?) -> String? {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM"
let dateString = formatter.string(from: date)
let monthDate = formatter.date(from: dateString)
formatter.dateFormat = "MMM,yyyy"
let dateMonth = formatter.string(from: monthDate!)
return dateMonth
}
override func tableView(_ tableView: UITableView,titleForHeaderInSection section: Int) -> String? {
guard let sectionInfo = fetchedResultsController.sections?[section] else {
return nil
}
return monthDate(string: sectionInfo.name)
}
我真的不明白我哪里出错了......我在 IOS14 和 Xcode 12.3 上使用 iPhone 11 pro max 模拟器。(我大约一个月前升级了它确实挂了大约 4 或5 天,从那以后就有点问题了)
虽然我确定我可能错过了一些非常简单的东西,但我们将不胜感激。
另一方面,如果我理解正确,我知道 NSFetchedResultsController 不喜欢这篇博文中的空白部分......
http://www.iosnomad.com/blog/2014/8/6/swift-nsfetchedresultscontroller-trickery
如果这可能是问题所在,Swift 5 是否有更简单的解决方案?
编辑
在我的 tableview didSelectRowAt 中我添加了
print(fetchedResultsController.object(at: indexPath))
并且控制台输出我保存的数据,但它仍然没有将它传递给我的 DetailViewController。
我也尝试在我的 prepareForSegue 添加
print(fetchedResultsController.object(at: selectedindexPath))
在选择我的单元格后,这没有返回保存的数据,并且在控制台上打印出 tableView 中的 numberOfSections 后返回 0 个部分。但是当我打开我的应用程序时,数据仍然保存在我的单元格中并可查看,并在我添加新注释时在控制台中打印出正确的部分编号。只有当我选择我的单元格将保存的数据传递给我的 DetailViewController 时才会出现问题。
解决方法
问题已解决
我在 UITableViewController 中添加了以下变量
var cellLabel: [Notes] = []
var selectedLabels = String()
var selectedTime = String()
并将变量添加到我的 DetailViewController
var receivedString: String = ""
var recievedTime: String = ""
我更改了代码以反映获取的对象附加到我的 didSelectRowAt 中的选定变量,并等于我在 prepareForSegue 中收到的变量。
override func tableView(_ tableView: UITableView,didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath,animated: true)
let event = fetchedResultsController.object(at: indexPath)
cellLabel = [event]
selectedLabels = event.noteName!
selectedTime = dateHelper.convertDate(date: Date.init(seconds: event.dateStamp))
print(selectedLabels)
print(selectedTime)
self.performSegue(withIdentifier: "showDetails",sender: self)
}
override func prepare(for segue: UIStoryboardSegue,sender: Any?) {
if segue.identifier == "showDetails" {
let controller = (segue.destination as! UINavigationController).topViewController as! DetailViewController
controller.receivedString = selectedLabels
controller.recievedTime = selectedTime
print(selectedLabels)
print(selectedTime)
/*
if let indexPath = self.tableView.indexPathForSelectedRow {
let objects = fetchedResultsController.object(at: indexPath)
controller.noteField.text = objects.noteName
controller.detailItem = objects
}
*/
}
}
在我的 viewDidLoad() 中的 DetailViewController
override func viewDidLoad() {
super.viewDidLoad()
noteField.text = receivedString
dateLabel.text = recievedTime
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。