微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Swift:UITableViewController 选择单元格并将获取的对象传递给 UIViewController

如何解决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 时才会出现问题。

This is a screenshot when I select using prepareForSegue & print out on the console

解决方法

问题已解决

我在 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

}

screenshot of fixed code

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。