【问题标题】:Swift: UITableViewController selecting cell & passing fetched objects to UIViewControllerSwift:UITableViewController 选择单元格并将获取的对象传递给 UIViewController
【发布时间】:2021-04-28 15:27:39
【问题描述】:

我正在尝试使用 Core Data 制作一个简单的笔记应用程序,但我遇到了 fetchedResultsController.object(at: indexPath) 和将数据传递到我的视图控制器的问题。使用 prepare(for segue: UIStoryboardSegue, sender: Any?)。

我的核心数据实体名为 Notes,具有以下属性

  • dateStamp 整数 64
  • noteName 字符串
  • noteImage 数据
  • noteDescription 字符串

但是,为了理解问题,我制作了单独的项目并将其限制为仅 noteName 和 dateStamp。

所以有 3 个 Controller 和 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,发件人: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, withEvent: anObject as! Notes, indexPath: indexPath!)
               
        self.performSegue(withIdentifier: "showDetails", sender: self)
    
       
    }

我添加了 self.performSegue(withIdentifier: "showDetails", sender: self) 但 DetailViewController 只是空白。

为了进一步理解,我在 cellForRowAt indexPath 中添加了按钮:...

cell.selectbutton.addTarget(self, action: #selector(selectNote(_:)), 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, animated: true, 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 时才会出现问题。

【问题讨论】:

  • 在基于故事板的 UI 中,默认初始化程序 MasterViewController() 创建控制器的一个新实例,该实例不是故事板中的实例。
  • 感谢您的评论。您能否进一步解释一下,我需要做些什么来更改我的代码以便我可以选择我的单元格?
  • 无论我在 selectNote 函数中所做的任何更改,它仍然会抛出错误,但只是为了澄清它的 prepareForSegue:sender: i need to work....

标签: swift uitableview core-data cell nsfetchedresultscontroller


【解决方案1】:

问题解决

我在 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
            
            }
                    */
   }
} 

在我的 DetailViewController 中的 viewDidLoad() 中

override func viewDidLoad() {
    super.viewDidLoad()
    
    noteField.text = receivedString
    dateLabel.text = recievedTime

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-05
    • 2020-11-14
    • 1970-01-01
    • 2018-03-04
    • 1970-01-01
    • 2017-03-25
    • 1970-01-01
    相关资源
    最近更新 更多