我创建了一个存储列表(例如名称)的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模型:
答案 0 :(得分:0)
您的代码行:
var people: [NSManagedObject] = []
当然是问题的核心。核心数据不知道此数组。删除该变量。您的表格视图数据源应该从获取的结果控制器获取数据,如下所示:
let person = fetchedResultsController()?.object(at: indexPath) as? Person
您应该能够在教程like this one或Apple示例代码中找到许多有效的UITableViewDataSource
代表的示例。遵循其中之一,以使自己成为一个好的常规表视图委托。上面的代码行来自little demo project which I forked recently。
另一个问题是,核心数据不会以任何特定顺序存储给定实体的托管对象。我在上一段中给出的链接都不支持排序。支持订购有两种选择:
替代方法1.使用Core Data排序关系。
如果您的人员是某种组实体的成员,则您的数据模型应该在组与人员之间具有多对多关系。您可以通过打开数据模型检查器中的 Ordered 复选框将此关系设置为有序关系。
替代方法2.使用 index 属性。
对于更简单的应用程序,这适用于您当前在屏幕截图中显示的数据模型,您可以将诸如 index 属性之类的属性添加到Person实体,并使获取的结果控制器按该属性排序:
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
let frc = NSFetchedResultsController(fetchRequest: fetchRequest,
managedObjectContext: managedContext,
sectionNameKeyPath: nil,
cacheName: nil)
其中我使用了您的符号名称managedContext
。
答案 1 :(得分:0)
添加“订单”属性-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]
...