2016-10-12 3 views
3

Я пытаюсь реализовать tableView с CoreData. Таблица имеет четыре способа сортировки. У меня не было проблем с реализацией первых трех, но четвертый был другим, поскольку это была сущность, которая имеет отношения. Во втором контроллере просмотра, где я могу добавлять элементы, я добавил функцию, которая извлекает информацию о существующих элементах и ​​отображает их относительные ячейки.tableView.cellForRow (at: indexPath) возвращает nil?

У приложения есть 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() 

    } 

} 

Я проверил ошибку и обнаружил, что он и возвращался Нилл из функции:

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

Я уверен в том, что в розетках и правильно настроил пользовательский класс, поэтому я протестировал и заметил, что при удалении определенной строки ошибка больше не появляется. Еще одна вещь, которую я заметил, заключается в том, что когда я запускаю приложение впервые в симуляторе, когда данные приложения не хранятся, он отлично работает даже с той строкой, которая вызвала проблему раньше, но после этого, когда я снова запускаю приложение показывает проблему. core data modelcore data modelcore data modelcore data modelerror Я искал причину, при которой возвращаемое значение cellForRow было бы полезным, но я не мог найти ничего полезного. Я надеюсь от вас помочь мне. Спасибо заранее.

решаемые Unwrap «клеток», используя, если позволить следующим образом:

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

Что такое ItemCell подкласс XIB или UITableViewCell, добавленный программно. Почему вы удалили строку для регистрации файла nib. Если вы это сделаете, если ячейка не существует во время выполнения дека, вам нужно создать ячейку, то есть «let cell = tableView.dequeueReusableCell (withIdentifier:« itemCell », для: indexPath) как! ItemCell if cell == nil {cell = ItemCell (.Default, reuseIdentifier: "itemCell")} ' –

+0

ItemCell - это подкласс, и, как я писал, он отлично работал в случае удаления конкретной строки - обратитесь к строке, которая начинается с «>>» в ItemVC. – Badr

+0

поместите одну строку в свой файл generateData() thats tableView.reloadData() и проверьте свой результат –

ответ

1

На основании вашего описания, это то, что я думаю, что происходит:

Вы нажмите на ячейку в ViewController, и перейдите к ItemVC. Когда вы сохраняете элемент, вызываются методы делегирования получателей результатов. В этом методе вы используете cellForRow(at:), чтобы получить ячейку, которая соответствует обновленному Item. Теперь эта строка должна быть видимой (хотя и «позади» ItemVC) - как еще вы могли ее использовать? - так cellForRow(at:) возвращает ячейку, которую необходимо настроить.

Вот как ваш код работал, и все было по-видимому, хорошо, пока вы не добавили строку, чтобы обновить отношения:

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

Вы говорите, что отношения с ItemType к Item является однозначным. Предположим, у вас есть itemA, который связан с «едой» ItemType. В вашем ViewController вы нажимаете на ячейку для другого элемента - itemB. В ItemVC вы обновляете атрибуты для itemB и назначаете его «пищевому продукту» ItemType. Но каждый ItemType может быть связан только с одним Item. Таким образом, связь от itemA к «пищевому» ItemType удаляется. Это означает, что значение toItemType для itemA установлено на ноль.Так два Item объекты поражаются:

  • itemA имеет toItemType набор к нулю, и
  • itemB имеет toItemType набор к "пище" ItemType.

Оба эти обновления соблюдаются FRC, и методы делегатов вызываются для каждого. В случае itemB все работает отлично, как раньше - это ячейка, которая была постукирована, чтобы она была видимой. Но в случае itemA он может быть или не быть видимым. Если он не отображается, cellForRow(at:) возвращает нуль, и ваш исходный код сработает. Используя необязательную привязку на cell, вы избегаете краха. Соответствующая строка не обновляется, но неважно: она не была видна в любом случае, и когда она прокручивается на экране, она будет правильно настроена как часть обычной обработки.

Ключевым моментом является то, что отношения от ItemType до Item являются одними, потенциально TWO-объекты обновляются при каждом обновлении в ItemVC. Я думаю, вы, возможно, захотите сделать это отношение ко многим. Затем добавление itemB к «пищевому продукту» ItemType не повлияет на itemA: они оба могут быть связаны с ним.

+0

Большое вам спасибо ... это не исправляет мой код, это очень важная информация для меня, как новичок в Core Data. – Badr