使用NSFetchedResultsController和Core Data移动时,UITableView单元格消失

时间:2017-11-13 09:40:16

标签: ios swift uitableview core-data nsfetchedresultscontroller

我正在构建我的第一个Core Data应用程序,并尝试实现一个功能,用户可以通过按住并拖动来移动单元格。

我得到的问题是当细胞释放时它们会消失。但是,如果向下滚动然后弹回,它们会重新出现,虽然按照原始顺序,而不是重新排列的顺序。

A screen-recording of the bug can be found here

知道我哪里出错了?

我的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
    ...
}

我的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 个答案:

答案 0 :(得分:0)

您的代码更新了表格视图的用户界面,但它不会更新您的数据模型以获得新订单。下次表格视图需要显示单元格时,代码会为其提供与拖动前相同的信息 - 例如,sortOrder仍会按旧顺序返回单元格,因为没有任何更改那里发生了什么。直到您滚动然后向后滚动才会发生这种情况,因为当表视图需要调用该方法时。旧订单将继续,只是因为您永远不会更改它,您只进行临时UI更新。

如果您希望能够重新订购表格中的项目,则需要更新数据模型以包含该订单。可能这意味着在Core Data中添加一个新字段,这个整数称为sortOrder。然后更新embedding = tf.get_variable("embedding", [1000000000, 20], partitioner=tf.fixed_size_partitioner(3)) ,使其与拖放中的新订单匹配。