tableView.cellForRow(at:indexPath)返回nil?

时间:2016-10-12 04:44:13

标签: ios swift core-data tableview xcode8

我正在尝试使用CoreData实现tableView。该表有四种排序方式。我实现前三个没有问题,但第四个是不同的,因为它是一个有关系的实体。 在我可以添加项目的第二个视图控制器中,我添加了一个功能,用于获取现有项目信息并将其显示为相关单元格。

该应用程序有2个viewControllers,一个用于tableView,另一个用于添加/编辑tableView正在查看的项目。 接下来是两个类:

import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, NSFetchedResultsControllerDelegate{

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var segment: UISegmentedControl!

    var controller: NSFetchedResultsController<Item>!

    override func viewDidLoad() {
        super.viewDidLoad()



        tableView.delegate = self
        tableView.dataSource = self

        //self.tableView.register(ItemCell.self, forCellReuseIdentifier: "ItemCell")

        generateData()
        attemptFetchRequest()
    }

    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 150
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        if let sections = controller.sections {

            let sectionInfo = sections[section]
            return sectionInfo.numberOfObjects
        }

        return 0
    }

    func numberOfSections(in tableView: UITableView) -> Int {

        if let sections = controller.sections {

            return sections.count
        }
        return 0
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "itemCell", for: indexPath) as! ItemCell

        configureCell(cell: cell, indexPath: indexPath as NSIndexPath)
        return cell

    }

    func configureCell (cell: ItemCell, indexPath: NSIndexPath) {
        let item = controller.object(at: indexPath as IndexPath)
        cell.configCell(item: item)
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        if let objs = controller.fetchedObjects , objs.count > 0 {
            let item = objs[indexPath.row]

            performSegue(withIdentifier: "ItemVC", sender: item)
        }
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ItemVC" {
            if let destination = segue.destination as? ItemVC {
                if let item = sender as? Item {
                    destination.itemtoEdit = item
                }
            }
        }
    }

    func attemptFetchRequest() {

        let fetchrequest: NSFetchRequest = Item.fetchRequest()


        let dateSort = NSSortDescriptor(key: "created", ascending: false)
        let priceSort = NSSortDescriptor(key: "price", ascending: true)
        let alphabetSort = NSSortDescriptor(key: "title", ascending: true)
        let typeSort = NSSortDescriptor(key: "toItemType.type", ascending: true)

        if segment.selectedSegmentIndex == 0 {
            fetchrequest.sortDescriptors = [dateSort]
        }else if segment.selectedSegmentIndex == 1 {
            fetchrequest.sortDescriptors = [priceSort]
        }else if segment.selectedSegmentIndex == 2 {
            fetchrequest.sortDescriptors = [alphabetSort]
        }else if segment.selectedSegmentIndex == 3{
            fetchrequest.sortDescriptors = [typeSort]
        }


        let controller = NSFetchedResultsController(fetchRequest: fetchrequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
        controller.delegate = self
        self.controller = controller

        do{
            try controller.performFetch()
        } catch {
            let error = error as NSError
            print("\(error)")
        }
    }

    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        tableView.beginUpdates()
    }

    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        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 = indexPath {
                >>let cell = tableView.cellForRow(at: indexPath) as! ItemCell
                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 segmentChanged(_ sender: AnyObject) {
        attemptFetchRequest()
        tableView.reloadData()
    }

    func generateData() {

        let item1 = Item(context: context)
        item1.title = "Car of the cars"
        item1.price = 100000
        item1.details = "Nothing much to say, it's a crapy car, don't buy it"

        let item2 = Item(context: context)
        item2.title = "Rocket"
        item2.price = 50000
        item2.details = "It's not fast as the actual rocket, but still faster than a bicycle"

        let item3 = Item(context: context)
        item3.title = "bal bla bla"
        item3.price = 50
        item3.details = "The price talks!"

        let item4 = Item(context: context)
        item4.title = "Old is Gold"
        item4.price = 60000000
        item4.details = "It's old, but also considered as great inheritance"
    }


}

和第二个视图控制器的类:

import UIKit
import CoreData

class ItemVC: UIViewController, UIPickerViewDelegate,                              UIPickerViewDataSource, UIImagePickerControllerDelegate,   UINavigationControllerDelegate {

    @IBOutlet weak var storesPicker: UIPickerView!
    @IBOutlet weak var name : UITextField!
    @IBOutlet weak var price : UITextField!
    @IBOutlet weak var details : UITextField!
    @IBOutlet weak var image: UIImageView!

    var stores = [Store]()
    var types = [ItemType]()

    var itemtoEdit: Item?

    var imagePicker: UIImagePickerController!

    override func viewDidLoad() {
        super.viewDidLoad()

        if let topItem = self.navigationController?.navigationBar.topItem {

            topItem.backBarButtonItem = UIBarButtonItem(title: "", style: UIBarButtonItemStyle.plain, target: nil, action: nil)
        }

        storesPicker.delegate = self
        storesPicker.dataSource = self

        imagePicker = UIImagePickerController()
        imagePicker.delegate = self

        generateData()

        fetchRequest()

        if itemtoEdit != nil {
            loadData()
        }

    }

    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {

        var returnValue = 0

        switch component {
        case 0:
            returnValue = stores.count
        case 1:
            returnValue = types.count
        default:
            break
        }

        return returnValue
    }

    func numberOfComponents(in pickerView: UIPickerView) -> Int {
        return 2
    }

    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {

        var returnValue : String!

        switch component {
        case 0:
            returnValue = stores[row].name
        case 1:
            returnValue = types[row].type
        default:
            break
        }

        print(returnValue)
        return returnValue
    }

    func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
        //update
    }

    func fetchRequest (){
        let storefetch : NSFetchRequest<Store> = Store.fetchRequest()
        let typefetch : NSFetchRequest<ItemType> = ItemType.fetchRequest()

        do {
            self.stores = try context.fetch(storefetch)
            self.types = try context.fetch(typefetch)
            self.storesPicker.reloadAllComponents()
        } catch {
            //print("fetch error")
        }
    }


    @IBAction func saveItem(_ sender: AnyObject) {

        var item : Item!

        let pic = Image(context: context)
        pic.image = image.image

        if itemtoEdit == nil {
            item = Item(context: context)
        } else {
            item = itemtoEdit
        }

        item.toImage = pic

        if let title = name.text{
            item.title = title
        }

        if let price = price.text {
            item.price = (price as NSString).doubleValue
        }

        if let details = details.text {
            item.details = details
        }

        item.toStore = stores[storesPicker.selectedRow(inComponent: 0)]

        >>item.toItemType = types[storesPicker.selectedRow(inComponent: 1)]



        ad.saveContext()

        _ = navigationController?.popViewController(animated: true)
        //dismiss(animated: true, completion: nil)
    }

    func loadData() {

        if let item = itemtoEdit {

            name.text = item.title
            price.text = "\(item.price)"
            details.text = item.details
            image.image = item.toImage?.image as? UIImage


            if let store = item.toStore {

            var index = 0

            repeat{


                if store.name == stores[index].name {

                    storesPicker.selectRow(index, inComponent: 0, animated: false)
                }

                index += 1
            } while(index < stores.count)
        }

            if let type = item.toItemType {

                var index = 0

                repeat{


                    if type.type! == types[index].type! {

                        storesPicker.selectRow(index, inComponent: 1, animated: false)
                    }

                    index += 1
                } while(index < types.count)
            }


        }
    }



    @IBAction func deleteItem(_ sender: UIBarButtonItem) {
        if itemtoEdit != nil {
            context.delete(itemtoEdit!)
            ad.saveContext()
        }
        _ = navigationController?.popViewController(animated: true)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        if let img = info[UIImagePickerControllerOriginalImage] as? UIImage {
            image.image = img
        }
        imagePicker.dismiss(animated: true, completion: nil)
    }

    @IBAction func setImg(_ sender: AnyObject) {

        present(imagePicker, animated: true, completion: nil)

    }

    func generateData(){

        let store1 = Store(context: context)
        store1.name = "Karfour"
        let store2 = Store(context: context)
        store2.name = "خير زمان"
        let store3 = Store(context: context)
        store3.name = "BestBuy"
        let store4 = Store(context: context)
        store4.name = "Virgin"
        let store5 = Store(context: context)
        store5.name = "Max"

        let type1 = ItemType(context: context)
        type1.type = "eletronics"
        let type2 = ItemType(context: context)
        type2.type = "food"
        let type3 = ItemType(context: context)
        type3.type = "wears"
        let type4 = ItemType(context: context)
        type4.type = "books"
        let type5 = ItemType(context: context)
        type5.type = "weapons"

        ad.saveContext()

    }

}

我测试了错误并发现它并且它从函数返回nill:

let cell = tableView.cellForRow(at: indexPath) as! ItemCell

我非常确定这些插座并以正确的方式设置自定义类,所以我测试并注意到当我删除某一行时,错误不再显示。我注意到的另一件事是,当我第一次在模拟器中运行应用程序时,没有应用程序的数据存储它,即使使用导致问题的行,但在此之后我完全正常工作 - 然后运行问题所显示的应用程序。 core data model core data model core data model core data model error 我搜索了导致cellForRow返回值的原因,但我找不到任何有用的东西。 我希望你能帮助我。 提前谢谢。

解决 打开&#34; cell&#34;使用if如下:

case .update:
        if let indexPath = indexPath {
            if let cell = tableView.cellForRow(at: indexPath) as? ItemCell {
                configureCell(cell: cell, indexPath: (indexPath as NSIndexPath))
            }
        }
        break

1 个答案:

答案 0 :(得分:1)

根据您的描述,我认为这是正在发生的事情:

点按ViewController中的单元格,然后转到ItemVC。保存项目时,将调用获取的结果控制器委托方法。在该方法中,您使用cellForRow(at:)来获取与已更新的Item对应的单元格。现在,该行必须是可见的(虽然“ItemVC后面”) - 你怎么能点击它? - 所以cellForRow(at:)会返回您需要配置的单元格。

这是你的代码如何工作,一切都显然没问题,直到你添加了更新关系的行:

item.toItemType = types[storesPicker.selectedRow(inComponent: 1)]

你说从ItemTypeItem的关系是一对一的。假设您有一个与“食物”itemA相关的ItemType。在ViewController中,您可以点击不同项目的单元格 - itemB。在ItemVC中,您更新itemB的属性并将其分配给“食物”ItemType。但每个ItemType只能与一个Item相关联。因此,从itemA到“食物”ItemType的关系将被删除。这意味着toItemType的{​​{1}}值设置为nil。所以两个itemA个对象受到影响:

  • ItemitemA设置为nil和
  • toItemTypeitemB设置为“食物”toItemType

FRC会观察到这两个更新,并为每个更新调用委托方法。在ItemType的情况下,一切都像以前一样正常 - 它是被轻敲的单元格,因此它必须是可见的。但是在itemB的情况下,它可能会也可能不会显示。如果它不可见,itemA将返回nil,原始代码崩溃。通过在cellForRow(at:)上使用可选绑定,可以避免崩溃。相应的行没有更新,但没关系:它无论如何都不可见,当它在屏幕上滚动时,它将被正确配置为正常处理的一部分。

关键是,因为从cellItemType的关系是一,所以每当您在Item中更新时,可能会更新两个对象。我想你可能想把这种关系变成很多。然后将ItemVC添加到“食物”itemB不会对ItemType产生任何影响:它们都可以与之相关。