I'm building my first Core Data app and am trying to implement a feature where users can move cells by holding and dragging.
我正在构建我的第一个Core Data应用程序,并且正在尝试实现一个功能,用户可以通过按住并拖动来移动单元格。
The problem I'm getting is that when the cells are released they disappear. However, if you scroll down enough and then pop back up, they reappear, albeit in their original order, not the rearranged order.
我得到的问题是,当细胞被释放时,它们会消失。但是,如果向下滚动然后再弹回,它们会重新出现,尽管是按照原始顺序,而不是重新排列的顺序。
A screen-recording of the bug can be found here
可以在此处找到错误的屏幕录制
Any idea where I'm going wrong?
知道我哪里错了吗?
My UITableViewController Sub-Class:
我的UITableViewController子类:
import UIKit
import CoreData
import CoreGraphics
class MainTableViewController: UITableViewController {
let context = AppDelegate.viewContext
lazy var fetchedResultsController: TodoFetchedResultsController = {
return TodoFetchedResultsController(managedObjectContext: self.context, tableView: self.tableView)
}()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = editButtonItem
fetchedResultsController.tryFetch()
let longPressRecognizer = UILongPressGestureRecognizer(target: self, action: #selector(longPressGestureRecognized(gestureRecognizer:)))
tableView.addGestureRecognizer(longPressRecognizer)
}
var snapshot: UIView? = nil
var path: IndexPath? = nil
@objc
func longPressGestureRecognized(gestureRecognizer: UILongPressGestureRecognizer) {
let state = gestureRecognizer.state
let locationInView = gestureRecognizer.location(in: tableView)
let indexPath = tableView.indexPathForRow(at: locationInView)
switch state {
case .began:
if indexPath != nil {
self.path = indexPath
let cell = tableView.cellForRow(at: indexPath!) as! TodoTableViewCell
snapshot = snapshotOfCell(cell)
var center = cell.center
snapshot!.center = center
snapshot!.alpha = 0.0
tableView.addSubview(snapshot!)
UIView.animate(withDuration: 0.1, animations: { () -> Void in
center.y = locationInView.y
self.snapshot!.center = center
self.snapshot!.transform = CGAffineTransform(scaleX: 1.05, y: 1.05)
self.snapshot!.alpha = 0.98
cell.alpha = 0.0
}, completion: { (finished) -> Void in
if finished {
cell.isHidden = true
}
})
}
case .changed:
if self.snapshot != nil {
var center = snapshot!.center
center.y = locationInView.y
snapshot!.center = center
if ((indexPath != nil) && (indexPath != self.path)) {
// Move cells
tableView.moveRow(at: self.path!, to: indexPath!)
self.path = indexPath
}
}
default:
if self.path != nil {
let cell = tableView.cellForRow(at: self.path!) as! TodoTableViewCell
cell.isHidden = false
cell.alpha = 0.0
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.snapshot!.center = cell.center
self.snapshot!.transform = CGAffineTransform.identity
self.snapshot!.alpha = 0.0
cell.alpha = 1.0
}, completion: { (finished) -> Void in
if finished {
cell.isHidden = true // FIXME: - Something up here?
}
})
}
}
}
func snapshotOfCell(_ inputView: UIView) -> UIView {
UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, false, 0.0)
guard let graphicsContext = UIGraphicsGetCurrentContext() else { fatalError() }
inputView.layer.render(in: graphicsContext)
et image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let cellSnapshot: UIView = UIImageView(image: image)
cellSnapshot.layer.masksToBounds = false
cellSnapshot.layer.cornerRadius = 0.0
cellSnapshot.layer.shadowOffset = CGSize(width: -5.0, height: 0.0)
cellSnapshot.layer.shadowRadius = 5.0
cellSnapshot.layer.shadowOpacity = 0.4
return cellSnapshot
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return fetchedResultsController.sections?.count ?? 0
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let section = fetchedResultsController.sections?[section] else { return 0 }
return section.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Todo Cell", for: indexPath) as! TodoTableViewCell
return configureCell(cell, at: indexPath)
}
private func configureCell(_ cell: TodoTableViewCell, at indexPath: IndexPath) -> TodoTableViewCell {
let todo = fetchedResultsController.object(at: indexPath)
do {
try cell.update(with: todo)
} catch {
print("\(error)")
}
return cell
}
func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
return true
}
// Support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let todoToDelete = fetchedResultsController.object(at: indexPath)
context.delete(todoToDelete)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
(UIApplication.shared.delegate as! AppDelegate).saveContext()
tableView.reloadData()
}
// MARK: - Navigation
...
}
My NSFetchedResultsControllerSubclass:
class TodoFetchedResultsController: NSFetchedResultsController<Todo>, NSFetchedResultsControllerDelegate {
private let tableView: UITableView
init(managedObjectContext: NSManagedObjectContext, tableView: UITableView) {
self.tableView = tableView
let request: NSFetchRequest<Todo> = Todo.fetchRequest()
let sortDescriptor = NSSortDescriptor(key: "dueDate", ascending: true)
request.sortDescriptors = [sortDescriptor]
super.init(fetchRequest: request, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
self.delegate = self
tryFetch()
}
func tryFetch() {
do {
try performFetch()
} catch {
print("Unresolved error: \(error)")
}
}
// MARK: - Fetched Results Controlle Delegate
// Handle insertion, deletion, moving and updating of rows.
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>,
didChange anObject: Any,
at indexPath: IndexPath?,
for type: NSFetchedResultsChangeType,
newIndexPath: IndexPath?) {
switch type {
case .insert:
if let insertIndexPath = newIndexPath {
self.tableView.insertRows(at: [insertIndexPath], with: .fade)
}
case .delete:
if let deleteIndexPath = indexPath {
self.tableView.deleteRows(at: [deleteIndexPath], with: .fade)
}
case .update:
if let updateIndexPath = indexPath {
if let cell = self.tableView.cellForRow(at: updateIndexPath) as! TodoTableViewCell? {
let todo = self.object(at: updateIndexPath)
do {
try cell.update(with: todo)
} catch {
print("error updating cell: \(error)")
}
}
}
case .move:
if let deleteIndexPath = indexPath {
self.tableView.deleteRows(at: [deleteIndexPath], with: .fade)
}
if let insertIndexPath = newIndexPath {
self.tableView.insertRows(at: [insertIndexPath], with: .fade)
}
break
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.reloadData()
}
}
1 个解决方案
#1
0
Your code updates the table view's UI, but it does not update your data model to have the new order. The next time your table view needs to display a cell, your code gives it the same information as before the drag-- For example, tableView(_:, cellForRowAt:)
still returns cells in the old order, because there's nothing that changes what happens there. This doesn't happen until you scroll away and then scroll back because that's when the table view needs to call that method. The old order will continue, simply because you never change it, you only make a temporary UI update.
您的代码更新了表视图的UI,但它不会更新您的数据模型以获得新订单。下次表视图需要显示一个单元格时,代码会给它提供与拖动之前相同的信息 - 例如,tableView(_:,cellForRowAt :)仍然按旧顺序返回单元格,因为没有什么可以改变什么发生在那里。直到您滚动然后向后滚动才会发生这种情况,因为当表视图需要调用该方法时。旧订单将继续,只是因为您永远不会更改它,您只进行临时UI更新。
If you want to make it possible to re-order items in the table, you need to update your data model to include that order. Probably that means adding a new field in Core Data, an integer called something like sortOrder
. Then update the sortOrder
so that it matches the new order from the drag and drop.
如果您希望能够重新排序表中的项目,则需要更新数据模型以包含该订单。可能这意味着在Core Data中添加一个新字段,这个整数称为sortOrder。然后更新sortOrder,使其与拖放中的新订单匹配。
#1
0
Your code updates the table view's UI, but it does not update your data model to have the new order. The next time your table view needs to display a cell, your code gives it the same information as before the drag-- For example, tableView(_:, cellForRowAt:)
still returns cells in the old order, because there's nothing that changes what happens there. This doesn't happen until you scroll away and then scroll back because that's when the table view needs to call that method. The old order will continue, simply because you never change it, you only make a temporary UI update.
您的代码更新了表视图的UI,但它不会更新您的数据模型以获得新订单。下次表视图需要显示一个单元格时,代码会给它提供与拖动之前相同的信息 - 例如,tableView(_:,cellForRowAt :)仍然按旧顺序返回单元格,因为没有什么可以改变什么发生在那里。直到您滚动然后向后滚动才会发生这种情况,因为当表视图需要调用该方法时。旧订单将继续,只是因为您永远不会更改它,您只进行临时UI更新。
If you want to make it possible to re-order items in the table, you need to update your data model to include that order. Probably that means adding a new field in Core Data, an integer called something like sortOrder
. Then update the sortOrder
so that it matches the new order from the drag and drop.
如果您希望能够重新排序表中的项目,则需要更新数据模型以包含该订单。可能这意味着在Core Data中添加一个新字段,这个整数称为sortOrder。然后更新sortOrder,使其与拖放中的新订单匹配。