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

标签 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()
    }
}

最佳答案

您的代码会更新 TableView 的 UI,但不会更新您的数据模型以获取新顺序。下次表格 View 需要显示单元格时,您的代码会为其提供与拖动之前相同的信息 - 例如,tableView(_:, cellForRowAt:) 仍按旧顺序返回单元格,因为没有什么可以改变那里发生的事情。直到您滚动然后再向后滚动时,这种情况才会发生,因为那时 TableView 需要调用该方法。旧的顺序将继续,只是因为您从未更改过它,您只是进行了临时的 UI 更新。

如果您希望能够对表中的项目进行重新排序,则需要更新数据模型以包含该顺序。可能这意味着在 Core Data 中添加一个新字段,一个名为 sortOrder 之类的整数。然后更新 sortOrder 以便它与拖放中的新顺序匹配。

关于ios - 使用 NSFetchedResultsController 和 Core Data 移动时 UITableView 单元格消失,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47261174/

相关文章:

ios - UITableview 单元格和标题间距

swift - 我们可以使用 Swift 在 Xcode 上的 Playground 上扩展一个类吗?

ios - 从 TableView Controller 处理多个 Segues 错误。

ios - 如何检查 UITableViewCell 是否包含 UIView

ios - 在 Swift 中添加引号

ios - swift 4.1 ISO8601DateFormatter 不解析 MRAID 广告内容传递的日期

json - SwiftyJSON/Alamofire 未将字符串解析为 UTF8

ios - UINavigtionController Nib 的顶部栏黑色不透明不在 UIViewController 上显示黑色

ios - 应用无法用 UUID 替换标签文本

ios - iTunes 连接 : "Release this version" delay?