我正在尝试使用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
我非常确定这些插座并以正确的方式设置自定义类,所以我测试并注意到当我删除某一行时,错误不再显示。我注意到的另一件事是,当我第一次在模拟器中运行应用程序时,没有应用程序的数据存储它,即使使用导致问题的行,但在此之后我完全正常工作 - 然后运行问题所显示的应用程序。 我搜索了导致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
答案 0 :(得分: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
的{{1}}值设置为nil。所以两个itemA
个对象受到影响:
Item
将itemA
设置为nil和toItemType
将itemB
设置为“食物”toItemType
。 FRC会观察到这两个更新,并为每个更新调用委托方法。在ItemType
的情况下,一切都像以前一样正常 - 它是被轻敲的单元格,因此它必须是可见的。但是在itemB
的情况下,它可能会也可能不会显示。如果它不可见,itemA
将返回nil,原始代码崩溃。通过在cellForRow(at:)
上使用可选绑定,可以避免崩溃。相应的行没有更新,但没关系:它无论如何都不可见,当它在屏幕上滚动时,它将被正确配置为正常处理的一部分。
关键是,因为从cell
到ItemType
的关系是一,所以每当您在Item
中更新时,可能会更新两个对象。我想你可能想把这种关系变成很多。然后将ItemVC
添加到“食物”itemB
不会对ItemType
产生任何影响:它们都可以与之相关。