将 UICollectionView 与 CoreData 和 NSFetchedResultsController 结合使用
Using UICollectionView with CoreData and NSFetchedResultsController
我最近开始了另一个项目 Swift 稍微探索一下。我想使用 NSFetchedResultsController 实现一个集合视图,以从我的 CoreData 数据库中获取数据。我想使用 https://github.com/AshFurrow/UICollectionView-NSFetchedResultsController 中的示例
并在 Swift 中实现类似的东西。我不需要任何移动事件,所以我实现了以下内容:
首先,我创建了一个 class 来保存所做的更改:
class ChangeItem{
var index:NSIndexPath
var type:NSFetchedResultsChangeType
init(index: NSIndexPath, type: NSFetchedResultsChangeType){
self.index = index
self.type = type
}
}
在我的集合视图控制器中,我使用两个数组来临时保存更改
var sectionChanges:[ChangeItem] = []
var objectChanges:[ChangeItem] = []
然后我等待我的 FetchedResultsController 在对 CoreData 数据库进行更改后更改某些内容
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?,forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
var item:ChangeItem?
if(type == NSFetchedResultsChangeType.Insert){
item = ChangeItem(index: newIndexPath!, type: type)
}else if(type == NSFetchedResultsChangeType.Delete){
item = ChangeItem(index: indexPath!, type: type)
}else if(type == NSFetchedResultsChangeType.Update){
item = ChangeItem(index: indexPath!, type: type)
}
if(item != nil){
self.objectChanges.append(item!)
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
var item:ChangeItem?
if(type == NSFetchedResultsChangeType.Insert || type == NSFetchedResultsChangeType.Delete || type == NSFetchedResultsChangeType.Update){
item = ChangeItem(index: NSIndexPath(forRow: 0, inSection: sectionIndex), type: type)
}
if(item != nil){
self.sectionChanges.append(item!)
}
}
应用所有更改后,FetchedResultsController 将执行 didChangeContent 方法
func controllerDidChangeContent(controller: NSFetchedResultsController) {
println("something happened")
self.collectionView!.performBatchUpdates({ () -> Void in
while(self.sectionChanges.count > 0 || self.objectChanges.count > 0){
self.insertSections()
self.insertItems()
self.updateItems()
let delips = self.deleteSections()
self.deleteItems(delips)
}
}, completion: { (done) -> Void in
if(done){
println("done")
self.collectionView.reloadData()
}else{
println("not done")
self.collectionView.reloadData()
}
if(self.fetchedResultsController.sections != nil && self.fetchedResultsController.sections?.count > 0){
println("number of items in first section \(self.fetchedResultsController.sections![0].count)")
}
})
self.deselectAll()
self.collectionView.reloadData()
if(self.fetchedResultsController.sections != nil && self.fetchedResultsController.sections!.count > 0){
for i in 1 ..< self.fetchedResultsController.sections!.count{
if(self.fetchedResultsController.sections!.count > 0){
println(self.fetchedResultsController.sections![i].numberOfObjects )
}
}
}
}
然后再次调用以下方法
private func deleteSections()->NSIndexSet{
var deletes = self.sectionChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Delete){
let index = (self.sectionChanges as NSArray).indexOfObject(item)
self.sectionChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexSet:NSMutableIndexSet = NSMutableIndexSet()
for del in deletes{
indexSet.addIndex(del.index.section)
}
if(indexSet.count > 0 && self.collectionView.numberOfSections() > 0){
self.collectionView.deleteSections(indexSet)
}
return indexSet
}
private func insertSections(){
var inserts = self.sectionChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Insert){
let index = (self.sectionChanges as NSArray).indexOfObject(item)
self.sectionChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexSet:NSMutableIndexSet = NSMutableIndexSet()
for ins in inserts{
indexSet.addIndex(ins.index.section)
}
if(indexSet.count > 0){
println("Adding \(indexSet.count) section")
println(indexSet)
self.collectionView.insertSections(indexSet)
self.collectionView.reloadSections(indexSet)
}
}
private func deleteItems(deletedSections:NSIndexSet?){
var deletes = self.objectChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Delete){
let index = (self.objectChanges as NSArray).indexOfObject(item)
self.objectChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexPaths:[NSIndexPath] = []
for del in deletes{
if(del.index.section < self.fetchedResultsController.sections?.count){
if(deletedSections == nil || deletedSections!.containsIndex(del.index.section)){
indexPaths.append(del.index)
}
}
}
/*indexPaths = indexPaths.sorted({ (a, b) -> Bool in
if(a.section >= b.section && a.row >= b.row){
return true
}
return false
})
println(indexPaths)*/
//self.collectionView.numberOfItemsInSection(0)
if(indexPaths.count > 0){
println("deleting \(indexPaths.count) items")
self.collectionView.deleteItemsAtIndexPaths(indexPaths)
}
}
private func updateItems(){
var updates = self.objectChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Update){
let index = (self.objectChanges as NSArray).indexOfObject(item)
self.objectChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexPaths:[NSIndexPath] = []
for update in updates{
indexPaths.append(update.index)
}
if(indexPaths.count > 0 ){
println("did update on \(indexPaths.count) items")
self.collectionView.reloadItemsAtIndexPaths(indexPaths)
}
}
private func insertItems(){
var inserts = self.objectChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Insert){
let index = (self.objectChanges as NSArray).indexOfObject(item)
self.objectChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexPaths:[NSIndexPath] = []
for ins in inserts{
indexPaths.append(ins.index)
}
if(indexPaths.count > 0 && self.numberOfSectionsInCollectionView(self.collectionView) > 0){
println("Adding \(indexPaths.count) items to collection view with \(self.collectionView.numberOfItemsInSection(0))")
self.collectionView.insertItemsAtIndexPaths(indexPaths)
println("Did add items")
}
}
一次删除多个项目和部分效果很好(到目前为止),但插入一个部分不起作用。如您所见,我在 insertSections 方法中实现了输出。我的测试场景中新部分的数量是 1,我得到了带有 1 个项目的正确 NSIndexSet:(0)。但是在 insertItems 方法中,我尝试在第 0 部分调用 numberOfObjects 并得到以下错误
*** Assertion failure in -[UICollectionViewData numberOfItemsInSection:], /SourceCache/UIKit_Sim/UIKit3347.44/UICollectionViewData.m:5942015-06-19:00:53:01.966 Grocli[42533:1547866] CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. request for number of items in section 0 when there are only 0 sections in the collection view with userInfo (null)
提前感谢您阅读那篇长文并帮助我!
听起来像是我前一段时间使用 objective C 时遇到的问题。
退房
UICollectionView Assertion failure
我必须解决问题才能让我的 FRC 与集合视图一起工作。
看来您发现了与我相同的 git,这是解决集合视图问题的修复程序。
https://github.com/AshFurrow/UICollectionView-NSFetchedResultsController/issues/13
这是我实现 FRC 委托方法的想法。
在这种情况下,对于 UICollectionViewController 子类:
Swift 3
import UIKit
import CoreData
class FetchedResultsCollectionViewController: UICollectionViewController, NSFetchedResultsControllerDelegate {
private var sectionChanges = [(type: NSFetchedResultsChangeType, sectionIndex: Int)]()
private var itemChanges = [(type: NSFetchedResultsChangeType, indexPath: IndexPath?, newIndexPath: IndexPath?)]()
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
sectionChanges.append((type, sectionIndex))
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
{
itemChanges.append((type, indexPath, newIndexPath))
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
collectionView?.performBatchUpdates({
for change in self.sectionChanges {
switch change.type {
case .insert: self.collectionView?.insertSections([change.sectionIndex])
case .delete: self.collectionView?.deleteSections([change.sectionIndex])
default: break
}
}
for change in self.itemChanges {
switch change.type {
case .insert: self.collectionView?.insertItems(at: [change.newIndexPath!])
case .delete: self.collectionView?.deleteItems(at: [change.indexPath!])
case .update: self.collectionView?.reloadItems(at: [change.indexPath!])
case .move:
self.collectionView?.deleteItems(at: [change.indexPath!])
self.collectionView?.insertItems(at: [change.newIndexPath!])
}
}
}, completion: { finished in
self.sectionChanges.removeAll()
self.itemChanges.removeAll()
})
}
}
我最近开始了另一个项目 Swift 稍微探索一下。我想使用 NSFetchedResultsController 实现一个集合视图,以从我的 CoreData 数据库中获取数据。我想使用 https://github.com/AshFurrow/UICollectionView-NSFetchedResultsController 中的示例 并在 Swift 中实现类似的东西。我不需要任何移动事件,所以我实现了以下内容:
首先,我创建了一个 class 来保存所做的更改:
class ChangeItem{
var index:NSIndexPath
var type:NSFetchedResultsChangeType
init(index: NSIndexPath, type: NSFetchedResultsChangeType){
self.index = index
self.type = type
}
}
在我的集合视图控制器中,我使用两个数组来临时保存更改
var sectionChanges:[ChangeItem] = []
var objectChanges:[ChangeItem] = []
然后我等待我的 FetchedResultsController 在对 CoreData 数据库进行更改后更改某些内容
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?,forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
var item:ChangeItem?
if(type == NSFetchedResultsChangeType.Insert){
item = ChangeItem(index: newIndexPath!, type: type)
}else if(type == NSFetchedResultsChangeType.Delete){
item = ChangeItem(index: indexPath!, type: type)
}else if(type == NSFetchedResultsChangeType.Update){
item = ChangeItem(index: indexPath!, type: type)
}
if(item != nil){
self.objectChanges.append(item!)
}
}
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
var item:ChangeItem?
if(type == NSFetchedResultsChangeType.Insert || type == NSFetchedResultsChangeType.Delete || type == NSFetchedResultsChangeType.Update){
item = ChangeItem(index: NSIndexPath(forRow: 0, inSection: sectionIndex), type: type)
}
if(item != nil){
self.sectionChanges.append(item!)
}
}
应用所有更改后,FetchedResultsController 将执行 didChangeContent 方法
func controllerDidChangeContent(controller: NSFetchedResultsController) {
println("something happened")
self.collectionView!.performBatchUpdates({ () -> Void in
while(self.sectionChanges.count > 0 || self.objectChanges.count > 0){
self.insertSections()
self.insertItems()
self.updateItems()
let delips = self.deleteSections()
self.deleteItems(delips)
}
}, completion: { (done) -> Void in
if(done){
println("done")
self.collectionView.reloadData()
}else{
println("not done")
self.collectionView.reloadData()
}
if(self.fetchedResultsController.sections != nil && self.fetchedResultsController.sections?.count > 0){
println("number of items in first section \(self.fetchedResultsController.sections![0].count)")
}
})
self.deselectAll()
self.collectionView.reloadData()
if(self.fetchedResultsController.sections != nil && self.fetchedResultsController.sections!.count > 0){
for i in 1 ..< self.fetchedResultsController.sections!.count{
if(self.fetchedResultsController.sections!.count > 0){
println(self.fetchedResultsController.sections![i].numberOfObjects )
}
}
}
}
然后再次调用以下方法
private func deleteSections()->NSIndexSet{
var deletes = self.sectionChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Delete){
let index = (self.sectionChanges as NSArray).indexOfObject(item)
self.sectionChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexSet:NSMutableIndexSet = NSMutableIndexSet()
for del in deletes{
indexSet.addIndex(del.index.section)
}
if(indexSet.count > 0 && self.collectionView.numberOfSections() > 0){
self.collectionView.deleteSections(indexSet)
}
return indexSet
}
private func insertSections(){
var inserts = self.sectionChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Insert){
let index = (self.sectionChanges as NSArray).indexOfObject(item)
self.sectionChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexSet:NSMutableIndexSet = NSMutableIndexSet()
for ins in inserts{
indexSet.addIndex(ins.index.section)
}
if(indexSet.count > 0){
println("Adding \(indexSet.count) section")
println(indexSet)
self.collectionView.insertSections(indexSet)
self.collectionView.reloadSections(indexSet)
}
}
private func deleteItems(deletedSections:NSIndexSet?){
var deletes = self.objectChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Delete){
let index = (self.objectChanges as NSArray).indexOfObject(item)
self.objectChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexPaths:[NSIndexPath] = []
for del in deletes{
if(del.index.section < self.fetchedResultsController.sections?.count){
if(deletedSections == nil || deletedSections!.containsIndex(del.index.section)){
indexPaths.append(del.index)
}
}
}
/*indexPaths = indexPaths.sorted({ (a, b) -> Bool in
if(a.section >= b.section && a.row >= b.row){
return true
}
return false
})
println(indexPaths)*/
//self.collectionView.numberOfItemsInSection(0)
if(indexPaths.count > 0){
println("deleting \(indexPaths.count) items")
self.collectionView.deleteItemsAtIndexPaths(indexPaths)
}
}
private func updateItems(){
var updates = self.objectChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Update){
let index = (self.objectChanges as NSArray).indexOfObject(item)
self.objectChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexPaths:[NSIndexPath] = []
for update in updates{
indexPaths.append(update.index)
}
if(indexPaths.count > 0 ){
println("did update on \(indexPaths.count) items")
self.collectionView.reloadItemsAtIndexPaths(indexPaths)
}
}
private func insertItems(){
var inserts = self.objectChanges.filter({ (item) -> Bool in
if(item.type == NSFetchedResultsChangeType.Insert){
let index = (self.objectChanges as NSArray).indexOfObject(item)
self.objectChanges.removeAtIndex(index)
return true
}
return false
}) as [ChangeItem]
var indexPaths:[NSIndexPath] = []
for ins in inserts{
indexPaths.append(ins.index)
}
if(indexPaths.count > 0 && self.numberOfSectionsInCollectionView(self.collectionView) > 0){
println("Adding \(indexPaths.count) items to collection view with \(self.collectionView.numberOfItemsInSection(0))")
self.collectionView.insertItemsAtIndexPaths(indexPaths)
println("Did add items")
}
}
一次删除多个项目和部分效果很好(到目前为止),但插入一个部分不起作用。如您所见,我在 insertSections 方法中实现了输出。我的测试场景中新部分的数量是 1,我得到了带有 1 个项目的正确 NSIndexSet:(0)。但是在 insertItems 方法中,我尝试在第 0 部分调用 numberOfObjects 并得到以下错误
*** Assertion failure in -[UICollectionViewData numberOfItemsInSection:], /SourceCache/UIKit_Sim/UIKit3347.44/UICollectionViewData.m:5942015-06-19:00:53:01.966 Grocli[42533:1547866] CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. request for number of items in section 0 when there are only 0 sections in the collection view with userInfo (null)
提前感谢您阅读那篇长文并帮助我!
听起来像是我前一段时间使用 objective C 时遇到的问题。
退房
UICollectionView Assertion failure
我必须解决问题才能让我的 FRC 与集合视图一起工作。
看来您发现了与我相同的 git,这是解决集合视图问题的修复程序。
https://github.com/AshFurrow/UICollectionView-NSFetchedResultsController/issues/13
这是我实现 FRC 委托方法的想法。
在这种情况下,对于 UICollectionViewController 子类:
Swift 3
import UIKit
import CoreData
class FetchedResultsCollectionViewController: UICollectionViewController, NSFetchedResultsControllerDelegate {
private var sectionChanges = [(type: NSFetchedResultsChangeType, sectionIndex: Int)]()
private var itemChanges = [(type: NSFetchedResultsChangeType, indexPath: IndexPath?, newIndexPath: IndexPath?)]()
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
sectionChanges.append((type, sectionIndex))
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?)
{
itemChanges.append((type, indexPath, newIndexPath))
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>)
{
collectionView?.performBatchUpdates({
for change in self.sectionChanges {
switch change.type {
case .insert: self.collectionView?.insertSections([change.sectionIndex])
case .delete: self.collectionView?.deleteSections([change.sectionIndex])
default: break
}
}
for change in self.itemChanges {
switch change.type {
case .insert: self.collectionView?.insertItems(at: [change.newIndexPath!])
case .delete: self.collectionView?.deleteItems(at: [change.indexPath!])
case .update: self.collectionView?.reloadItems(at: [change.indexPath!])
case .move:
self.collectionView?.deleteItems(at: [change.indexPath!])
self.collectionView?.insertItems(at: [change.newIndexPath!])
}
}
}, completion: { finished in
self.sectionChanges.removeAll()
self.itemChanges.removeAll()
})
}
}