【问题标题】:Reorder UITableviewCell where data is stored in CoreData对 UITableviewCell 重新排序,其中数据存储在 CoreData 中
【发布时间】:2019-10-01 12:53:45
【问题描述】:

我创建了一个 iOS 应用程序来存储一个列表(比如名称)。此外,我还添加了滑动删除、搜索和重新排序表格行等功能。以下是应用截图:

我面临的问题是,当我首先通过单击编辑按钮重新排序列表时,似乎代码工作正常。在下面的屏幕中,我交换了前两行,这似乎是我想要的。 看下面两张截图:

但是当我执行搜索功能时,已互换的行会恢复到其原始位置,如第一张图片所示。因为我使用 CoreData 作为持久存储。我正在尝试找到解决方案,但到目前为止还没有成功。这是执行搜索功能后的样子:

这是我的代码:

import UIKit
import CoreData

class ViewController: UIViewController, UISearchBarDelegate, UISearchDisplayDelegate {

    // IBOutlets
    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var searchBar: UISearchBar!
    @IBOutlet weak var editButton: UIBarButtonItem!

    // Global declaration
    var people: [NSManagedObject] = []

    // Below is a computed property
    var appDelegate: AppDelegate {
        return UIApplication.shared.delegate as! AppDelegate
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        // The below line is for giving a title to the View Controller.
        // title = "The List"

        searchBar.delegate = self

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")

        // The below line hides empty rows in a tableView
        tableView.tableFooterView = UIView()

    }

    //MARK: - IBAction addName implementation
    @IBAction func addName(_ sender: UIBarButtonItem) {
        let alert = UIAlertController(title: "New Name", message: "Add a new name", preferredStyle: .alert)
        let saveAction = UIAlertAction (title: "Save", style: .default) {
            [unowned self] action in
            guard let textField = alert.textFields?.first, let nameToSave = textField.text else {
                return
            }
            self.save(name: nameToSave)
            self.tableView.reloadData() // this is to reload the table data
        }

        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)

        // The below code handles the validation operation. In this case, we are checking wheather the field is empty and if it is 'save' button is disabled.
        alert.addTextField(configurationHandler: { (textField) in
            textField.text = ""
            textField.placeholder = "Enter something...."
            saveAction.isEnabled = false
            NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: OperationQueue.main) { (notification) in
                saveAction.isEnabled = textField.text!.count > 0
            }
        })

        alert.addAction(saveAction)
        alert.addAction(cancelAction)

        present(alert, animated: true)
    }

    // MARK: - SAVING TO CORE DATA

    // CoreData kicks in here!
    func save(name: String) {

        // 1
        let managedContext = appDelegate.persistentContainer.viewContext

        // 2
        let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)!
        let person = NSManagedObject(entity: entity, insertInto: managedContext)

        // 3
        person.setValue(name, forKeyPath: "name")

        // 4
        do {
            try managedContext.save()
            people.append(person)
        } catch let error as NSError {
            print("Could not save. \(error), \(error.userInfo)")
        }
    }

    // MARK: - FETCHING FROM CORE DATA

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        // 1
        let managedContext = appDelegate.persistentContainer.viewContext

        // 2
        let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")

        // 3
        do {
            people = try
                managedContext.fetch(fetchRequest)
        } catch let error as NSError {
            print("Could not save. \(error), \(error.userInfo)")
        }
    }

    // MARK: - searchBar functionality implementation

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        if searchText != "" {
            var predicate: NSPredicate = NSPredicate()
            predicate = NSPredicate(format: "name contains[c] '\(searchText)'")
            let context = appDelegate.persistentContainer.viewContext
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
            fetchRequest.predicate = predicate
            do {
                people = try context.fetch(fetchRequest) as! [NSManagedObject]
            } catch {
                print("Could not get search data!")
            }
        } else {
            let context = appDelegate.persistentContainer.viewContext
            let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Person")
            do {
                people = try context.fetch(fetchRequest) as! [NSManagedObject]
            } catch {
                print("Error in loading data.")
            }
        }
        tableView.reloadData() // This line reloads the table whether search is performed or not.
    }

    // This function closes the search bar when cancel button is tapped.
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
        searchBar.resignFirstResponder()
    }

    @IBAction func editButton(_ sender: Any) {
        tableView.isEditing = !tableView.isEditing

        // This switch case is for changing the title when editing
        switch tableView.isEditing {
        case true:
            editButton.title = "Done"
        case false:
            editButton.title = "Edit"
        }
    }

}

// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return people.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let person = people[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = person.value(forKeyPath: "name") as? String
        return cell
    }

    // This function sets the height for a row programatically.
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 55.0
    }

    // Determine whether a given row is eligible for reordering or not.
    func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    // Process the row move. This means updating the data model to correct the item indices.
    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
        let itemToMove =  people.remove(at: sourceIndexPath.row)
        people.insert(itemToMove, at: destinationIndexPath.row)
        tableView.reloadData()
    }

}

// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        return true
    }

    func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            // handle delete (by removing the data from your array and updating the tableview)

            // MARK: - Delete the person from core data

            let person = people[indexPath.row]
            let managedContext = appDelegate.persistentContainer.viewContext
            managedContext.delete(person)
            try? managedContext.save() // This the short version of do-catch block used in above functions to save and fetch data

            // remove the person from cache / CoreData
            people.remove(at: indexPath.row)

            // delete row from table view
            tableView.deleteRows(at: [indexPath], with: .automatic)
        }
    }

}

任何帮助将不胜感激,因为我已经尝试了数周。

我的 CoreData 模型:

【问题讨论】:

    标签: uitableview core-data xcode10 ios12


    【解决方案1】:

    你的代码行:

    var people: [NSManagedObject] = []
    

    当然是问题的核心。 Core Data 不知道这个数组。删除该变量。您的表视图数据源应该从获取的结果控制器中获取其数据,如下所示:

    let person = fetchedResultsController()?.object(at: indexPath) as? Person
    

    您应该能够在教程like this one 或Apple 示例代码中找到许多工作UITableViewDataSource 代表的示例。遵循其中一个,让自己成为一个优秀的、传统的 table view 委托。上面这行代码来自little demo project which I forked recently

    另一个问题是Core Data 不会以任何特定顺序存储给定实体的托管对象。我在上一段中给出的链接都不支持排序。支持排序有两种选择:

    备选方案 1. 使用 Core Data 有序关系。

    如果您的 Persons 是某种 Group 实体的成员,则您的数据模型应该具有从 Group 到 Person 的一对多关系。您可以通过在 Data Model 检查器 中为这种关系打开 Ordered 复选框来使其成为有序关系。

    备选方案 2. 使用 index 属性。

    对于更简单的应用程序,并且这适用于当前在屏幕截图中的数据模型,您可以向您的 Person 实体添加诸如 index 属性之类的属性,并使您的获取结果控制器按此属性排序:

    let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
    fetchRequest.sortDescriptors = [sortDescriptor]
    let frc = NSFetchedResultsController(fetchRequest: fetchRequest,
                                 managedObjectContext: managedContext,
                                   sectionNameKeyPath: nil,
                                            cacheName: nil)
    

    其中我使用了您的符号名称managedContext

    【讨论】:

    • 我已经用我的 CoreData 模型的屏幕截图更新了这个问题。我只有一个实体是“人”,只有一个属性是“名称”。我的问题是我是否需要创建另一个实体或属性来保存重新排序的列表?另外我不知道如何使用“fetchResultController”从 CoreData 模型中获取数据。
    【解决方案2】:

    添加属性“order” - Int16

    func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
       if sourceIndexPath.row > destinationIndexPath.row {
            for i in destinationIndexPath.row..<sourceIndexPath.row {
                Items[i].setValue(i+1, forKey: "order")
            }
    
            Items[sourceIndexPath.row].setValue(destinationIndexPath.row, forKey: "order")
        }
    
        if sourceIndexPath.row < destinationIndexPath.row {
            for i in sourceIndexPath.row + 1...destinationIndexPath.row {
                Items[i].setValue(i-1, forKey: "order")
            }
    
            Items[sourceIndexPath.row].setValue(destinationIndexPath.row, forKey: "order")
        }
    
     //Save
    }
    

    加载

    ...

    let orderSort = NSSortDescriptor(key: "order", ascending: true)
    fetchRequest.sortDescriptors = [orderSort]
    

    ...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-05-01
      • 1970-01-01
      • 2022-01-01
      • 1970-01-01
      • 2015-12-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多