简体   繁体   English

使用NSFetchedResultsController核心数据重新排序TableView单元格 - Swift 3

[英]Reorder TableView Cells With NSFetchedResultsController Core Data - Swift 3

I am using an NSFetchedResultsController. 我正在使用NSFetchedResultsController。 I can't find any straightforward tutorials that walk through it for Swift 3 . 我找不到任何直接的教程,为Swift 3提供了它。

So here is what I have done so far. 所以这就是我到目前为止所做的。 I have successfully populated my table using an NSFetchedResultsController that fetches the inserted data from core data. 我已经使用NSFetchedResultsController成功填充了我的表,该NSFetchedResultsController从核心数据中提取插入的数据。 I created an attribute in my core data model called, orderPosition that is declared as Int32 . 我在我的核心数据模型中创建了一个名为orderPosition的属性,该属性声明为Int32 I haven't done anything with this attribute in regards to adding and saving data to core data upon insert. 关于在插入时向核心数据添加和保存数据,我没有对此属性进行任何操作。

In my fetch func where I initialize the NSFetchedResultsController , I have modified my sort descriptor to include the following code: 在我初始化NSFetchedResultsController fetch func中,我修改了我的排序描述符以包含以下代码:

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

Then I implemented the tableView func: 然后我实现了tableView函数:

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {

    attemptFetch()
    var objects = self.controller.fetchedObjects! 
    self.controller.delegate = nil

    let object = objects[sourceIndexPath.row]
    objects.remove(at: sourceIndexPath.row)
    objects.insert(object, at: destinationIndexPath.row)

    var i = 0
    for object in objects {
        object.setValue(i += 1, forKey: "orderPosition")
    }

    appdel.saveContext()
    self.controller.delegate = self

}

In my implementation of the didChange anObject func, I included the switch-case for .insert , .delete , .update , and .move . 在我的didChange anObject函数的实现中,我包含了.insert.delete.update.move的switch-case。 My .move case looks like the following: 我的.move案例如下所示:

case.move:
        if let indexPath = indexPath {
            tableView.deleteRows(at: [indexPath], with: .fade)
        }
        if let indexPath = newIndexPath {
            tableView.insertRows(at: [indexPath], with: .fade)
        }
        break

Please help me figure this out. 请帮我解决这个问题。 I have been spending weeks trying to understand this. 我花了几周时间试图理解这一点。 I have gone through many if not all stack overflow questions on this. 我已经经历了很多(如果不是全部)堆栈溢出问题。 I implemented some of the loop ideas from one because I had a similar thought process to approach this but it didn't work. 我从一个实现了一些循环思想,因为我有一个类似的思考过程来处理这个但它没有用。 There are no Swift 3 core data tutorials/videos that I have come across that will really help me solve and understand this well. 没有我遇到的Swift 3核心数据教程/视频,这将真正帮助我解决和理解这一点。 I don't want to give up on this. 我不想放弃这个。

Swift deviates from C in that the =, += and other assignment-like operations returns Void . Swift偏离C,因为=,+ =和其他类似赋值的操作返回Void So your line object.setValue(i += 1, forKey: "orderPosition") is setting i to be one more but is always setting the position to be 0. Try instead: 所以你的行object.setValue(i += 1, forKey: "orderPosition")i设置为一个,但总是将位置设置为0.请改为:

var i = 0
for object in objects {
    i += 1
    object.setValue(i, forKey: "orderPosition")
}

or if you want to simplify your code a bit you can do 或者如果你想简化你的代码,你可以做一点

for (i, object) in objects.enumerated() {
    object.setValue(i, forKey: "orderPosition")
}

Moving around: 到处走:

    var objects = frc.fetchedObjects!

    // Disable fetchedresultscontroller updates.

    let obj objects.remove(at: indexPath.item)
    objects.insert(obj, at: newIndexPath)

    for (i, obj) in objects.enumerated() {
        obj.orderIndex = Int32(i)
    }

    // Enable fetchedresultscontroller updates.

    context.saveContext()

TLDR: Disable updates from the fetchedresultscontroller cause else things crash on you, or use a workaround which does not crash on you. TLDR:禁用fetchedresultscontroller的更新导致其他事情崩溃,或使用不会崩溃的解决方法。

My full tale on why to lock. 关于为什么要锁定我的全部故事。

The disable stuff is there cause the tableview moved the cell already. 禁用的东西是因为tableview已经移动了单元格。 If you then set the orderIndex, it will try to move things again which might crash on you as it did on me. 如果你然后设置orderIndex,它将尝试再次移动,这可能会像你一样在你身上崩溃。

My workaround to this is to not have a .move case 我的解决方法是没有.move案例

    case .update, .move:
        print("update")
        self.reloadRows(at: [indexPath!], with: .automatic)

which I do not need anyway cause I move only with the tableview for now. 我不需要,因为我现在只用tableview移动。 If you want to have the fetchedresultscontroller move stuff too you have to not have this case .update, .move but handle the .move yourself like 如果你想让fetchedresultscontroller移动东西,你必须没有这种情况.update,.move但是自己处理.move

    case .move:
        print("move")
        self.moveRow(at: indexPath!, to: newIndexPath!)

but don't forget to implement the locking or my alternative below then. 但是不要忘记在下面实施锁定或我的替代方案。

Another workaround to not crash on me on endUpdates was 在endUpdates上没有崩溃的另一个解决方法是

    if let toIndexPath = newIndexPath {
        if type == .update && toIndexPath != indexPath!{
            print("it should be a move.")
            print("move")
            self.moveRow(at: indexPath!, to: newIndexPath!)
            return
        }
    }

which detects updates for which newIndexPath != indexPath(the old index path), essentially it detects moves which are not declared as moves and fixed the problem for me too, but it was just too weird for me. 它检测newIndexPath!= indexPath(旧索引路径)的更新,基本上它检测未被声明为移动的移动并为我修复问题,但这对我来说太奇怪了。

The following is the fetchedresultscontrollers' answer to my orderIndex changes, which should explain why you, incase you want to have the fetchedresultscontroller move stuff to better implement my "move disguised as update" detection or disable updates to table during the updating of the objects, which is fine cause the table reordered cells itself already by your move. 以下是fetchedresultscontrollers对我的orderIndex更改的回答,这可以解释为什么你,想要让fetchedresultscontroller移动东西来更好地实现我的“移动伪装为更新”检测或在更新对象期间禁用对表的更新,这很好,因为桌子已经通过你的移动重新排序了细胞本身。

    from: Optional([0, 2])
    to: Optional([0, 1])
    update
    from: Optional([0, 1])
    to: Optional([0, 0])
    update
    from: Optional([0, 0])
    to: Optional([0, 2])
    move
    ... Assertion failure in ...
    attempt to perform an insert and a move to the same index path (<NSIndexPath: 0xc000000000400016> {length = 2, path = 0 - 2})
    (null)

edit: 编辑:

I think the "move disguised as update" detection does not produce crap cause it simply reloads all the cells. 我认为“移动伪装成更新”检测不会产生垃圾,因为它只是重新加载所有细胞。 Cause then it moved the tableviewcell 1 time by the tableview itself + 3 times by the detection(useless). 因此,它通过tableview本身将tableviewcell移动1次,通过检测移动3次(无用)。

Why is fetchedresultscontroller using this call-pattern? 为什么fetchedresultscontroller使用这个调用模式? It probably frantically tries to get the table in order as if the updates came from outside and the table needs updating. 它可能会疯狂地尝试按顺序获取表,就像更新来自外部并且表需要更新一样。 But it does not know that the table cells got put in the right order already by the tableview, so it crashes hard on super.endUpdates() for some reason. 但是它不知道表格单元格已经由tableview以正确的顺序排列,因此由于某种原因它在super.endUpdates()上崩溃了。

SOO: probably most simple solution is to disable updates to the tableview. SOO:可能最简单的解决方案是禁用对tableview的更新。 How? 怎么样? No idea right now, I'm going to bed. 现在不知道,我要睡觉了。

Just implemented similar functionality in my project.Turned out to be easier than it seemed. 刚刚在我的项目中实现了类似的功能。变得比看起来更容易。

Here is an update for your moveRowAt method 这是您的moveRowAt方法的更新

func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {

    // NOT NEEDED, SINCE I ASSUME THAT YOU ALREADY FETCHED ON INITIAL LOAD
    // attemptFetch()

    var objects = self.controller.fetchedObjects! 

    // NOT NEEDED
    //self.controller.delegate = nil

    let object = objects[sourceIndexPath.row]
    objects.remove(at: sourceIndexPath.row)
    objects.insert(object, at: destinationIndexPath.row)

    // REWRITEN BELOW
    //var i = 0
    //for object in objects {
    //    object.setValue(i += 1, forKey: "orderPosition")
    //}

    for (index, item) in items.enumerated() {
        item.orderPosition = index
    }

    appdel.saveContext()
    //self.controller.delegate = self

}

In didChange technically you don't need any code. didChange技术上讲,您不需要任何代码。 The table is updated in place, when you move the item in it. 当您移动项目时,表格会就地更新。 And your data in coreData is updated when you save the context. 保存上下文时,coreData中的数据会更新。 So next time the data is fetched, based on the sortDescriptor you have, it will come in sorted by orderPosition. 因此,下次提取数据时,根据您拥有的sortDescriptor,它将按orderPosition排序。

Worked for me. 为我工作。 Let me know, if you have any questions. 如果您有任何疑问,请与我们联系。

Assuming core data & fetchedResultsController: regarding moveRowAt, the from and to index path rows are within the section. 假设核心数据和fetchedResultsController:关于moveRowAt,from和to索引路径行在该部分内。 If you are moving between the sections you need to work out the actual row to move in your array. 如果要在各部分之间移动,则需要计算实际行以在数组中移动。 ie: if you are moving the second row in the third section to the second row in the first section you actually need to know how many rows there are in all the sections to remove the correct row and insert at the correct place. 即:如果您要将第三部分中的第二行移动到第一部分中的第二行,您实际上需要知道所有部分中有多少行要删除正确的行并插入到正确的位置。

I use the following method to get the correct rows:- 我使用以下方法来获取正确的行: -

    // Do not trigger delegate methods when changes are made to core data by the user
        fetchedResultsController.delegate = nil
        var fromIndex = fromIndexPath.row
        var toIndex = toIndexPath.row
        //      print ("from row ",fromIndexPath.row)
//work out correct row to remove at based on it's current row and section
        for sectionIndex in 0..<fromIndexPath.section
        {
            fromIndex += fetchedResultsController.sections![sectionIndex].numberOfObjects
        }
            //print ("fromIndex ",fromIndex)
// remove the row at it's old position
       toDoData.remove(at: fromIndex)
//work out the correct row to insert at based on which section it's going to & row in that section
        for sectionIndex in 0..<toIndexPath.section
        {
            toIndex += fetchedResultsController.sections![sectionIndex].numberOfObjects
            //print ("toIndex ",toIndex)
            if sectionIndex == fromIndexPath.section
            {
                toIndex -= 1 // Remember, controller still thinks this item is in the old section
                //print ("-= toIndex",toIndex)
            }
        }
// put the item back into he array at new position
         toDoData.insert(item, at: toIndex)

Note: toDoData is my array of records from the fetchedResultsController 注意:toDoData是来自fetchedResultsController的记录数组

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 将核心数据快速检索到tableview单元格中 - retrieving core data into tableview cells swift 在UICollectionView + NSFetchedResultsController中对单元格重新排序 - Reorder cells in UICollectionView + NSFetchedResultsController Swift-如何通过其属性重新排列tableview中的所有单元格? - Swift - How to reorder all the cells in the tableview by its attribute? 核心数据NSFetchedResultsController一对多快速获取titleForHeader - Core Data NSFetchedResultsController one to many for swift titleForHeader Swift:在使用NSFetchedResultsController谓词后重新加载tableview数据 - Swift: Reload tableview data after applying a predicate with NSFetchedResultsController 从 tableView 中的 Firestore 单元格读取数据后随机重新排序 - After reading data from Firestore cells in tableView randomly reorder Swift使用NSFetchedResultsController更新TableView - Swift Using NSFetchedResultsController to update TableView 使用NSFetchedResultsController和Core Data移动时,UITableView单元消失 - UITableView cells disappearing when moving using NSFetchedResultsController and Core Data 将TableView单元格中的数据保存到核心数据中 - Saving data from tableview cells in core data 使用核心数据重新排列TableView单元格的顺序 - Rearranging order of TableView cells using Core Data
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM