-
-
Save iwasrobbed/5528897 to your computer and use it in GitHub Desktop.
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller | |
{ | |
self.shouldReloadCollectionView = NO; | |
self.blockOperation = [[NSBlockOperation alloc] init]; | |
} | |
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo | |
atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type | |
{ | |
__weak UICollectionView *collectionView = self.collectionView; | |
switch (type) { | |
case NSFetchedResultsChangeInsert: { | |
[self.blockOperation addExecutionBlock:^{ | |
[collectionView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; | |
}]; | |
break; | |
} | |
case NSFetchedResultsChangeDelete: { | |
[self.blockOperation addExecutionBlock:^{ | |
[collectionView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; | |
}]; | |
break; | |
} | |
case NSFetchedResultsChangeUpdate: { | |
[self.blockOperation addExecutionBlock:^{ | |
[collectionView reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]]; | |
}]; | |
break; | |
} | |
default: | |
break; | |
} | |
} | |
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath | |
{ | |
__weak UICollectionView *collectionView = self.collectionView; | |
switch (type) { | |
case NSFetchedResultsChangeInsert: { | |
if ([self.collectionView numberOfSections] > 0) { | |
if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) { | |
self.shouldReloadCollectionView = YES; | |
} else { | |
[self.blockOperation addExecutionBlock:^{ | |
[collectionView insertItemsAtIndexPaths:@[newIndexPath]]; | |
}]; | |
} | |
} else { | |
self.shouldReloadCollectionView = YES; | |
} | |
break; | |
} | |
case NSFetchedResultsChangeDelete: { | |
if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) { | |
self.shouldReloadCollectionView = YES; | |
} else { | |
[self.blockOperation addExecutionBlock:^{ | |
[collectionView deleteItemsAtIndexPaths:@[indexPath]]; | |
}]; | |
} | |
break; | |
} | |
case NSFetchedResultsChangeUpdate: { | |
[self.blockOperation addExecutionBlock:^{ | |
[collectionView reloadItemsAtIndexPaths:@[indexPath]]; | |
}]; | |
break; | |
} | |
case NSFetchedResultsChangeMove: { | |
[self.blockOperation addExecutionBlock:^{ | |
[collectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; | |
}]; | |
break; | |
} | |
default: | |
break; | |
} | |
} | |
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller | |
{ | |
// Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582 | |
if (self.shouldReloadCollectionView) { | |
[self.collectionView reloadData]; | |
} else { | |
[self.collectionView performBatchUpdates:^{ | |
[self.blockOperation start]; | |
} completion:nil]; | |
} | |
} |
make a loc. var for it
@mangoldm As mickspecial said, it's just an instance variable in the class. Nowadays, I probably would create it as a private property in the class so it would be self.blockOperation
.. More info here: https://github.com/iwasrobbed/Objective-C-CheatSheet#properties-and-variables
Got to your gist from here https://github.com/AshFurrow/UICollectionView-NSFetchedResultsController/issues/13
Thank you for this! Works really well for my use case so far.
Thanks for the integration, great gist! Just one thing, indexPath
on line 44 is always nil for insert, so it should be newIndexPath
. Also, I don't think line 43 is necessary as the if check already covers this case.
Rewrote your Gist in Swift:
https://gist.github.com/Navitor/0ce6b782a5428bd17904
UICollectionView and NSFectchedResultsControllerDelegate integration in Swift:
https://gist.github.com/AppsTitude/ce072627c61ea3999b8d
Hey guys, just mind that this is unsafe as it performs updates on the UICollectionViews on the non-main thread. Solution would be to schedule the execution of the blocks on the current thread:
[[NSOperationQueue currentQueue] addOperation:self.blockOperation];
@MatejBalantic, according Apple Docs:
Operation objects are synchronous by default. In a synchronous operation, the operation object does not create a separate thread on which to run its task. When you call the start method of a synchronous operation directly from your code, the operation executes immediately in the current thread. By the time the start method of such an object returns control to the caller, the task itself is complete.
@pomozoff This is incorrect. When creating your own NSOperation subclass, it will operate synchronously if you call start, but an NSBlockOperation is different. From the NSBlockOperation Apple docs:
"The NSBlockOperation class is a concrete subclass of NSOperation that manages the concurrent execution of one or more blocks. You can use this object to execute several blocks at once without having to create separate operation objects for each......"
"Blocks added to a block operation are dispatched with default priority to an appropriate work queue. The blocks themselves should not make any assumptions about the configuration of their execution environment."
I understand that internally the block operation is dispatching the blocks to background threads, and indeed logging out the thread number from within the queued blocks yields:
2015-10-26 09:31:10.414 [76548:1833049] <NSThread: 0x7e922e80>{number = 37, name = (null)}
2015-10-26 09:31:10.414 [76548:1833062] <NSThread: 0x7bf81b80>{number = 33, name = (null)}
2015-10-26 09:31:10.414 [76548:1831231] <NSThread: 0x7d1380b0>{number = 1, name = main}
2015-10-26 09:31:10.414 [76548:1833061] <NSThread: 0x7be2efd0>{number = 35, name = (null)}
2015-10-26 09:31:10.414 [76548:1833063] <NSThread: 0x7e823f10>{number = 36, name = (null)}
2015-10-26 09:31:10.414 [76548:1833037] <NSThread: 0x7d2c0b30>{number = 34, name = (null)}
2015-10-26 09:31:10.414 [76548:1833064] <NSThread: 0x7d5273b0>{number = 32, name = (null)}
2015-10-26 09:31:10.414 [76548:1833050] <NSThread: 0x7d720e70>{number = 38, name = (null)}
OK. scrap that ...
@MatejBalantic you're correct about using [[NSOperationQueue currentQueue] addOperation:self.blockOperation];
v.s. [self.blockOperation start];
. If I use blockOperation.start
, NSFetchedResultsChangeUpdate
events aren't always processed properly (or at all).
Here's an updated controllerDidChangeContent
method that works for me:
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// Checks if we should reload the collection view to fix a bug @ http://openradar.appspot.com/12954582
if (self.shouldReloadCollectionView) {
[self.collectionView reloadData];
} else {
[self.collectionView performBatchUpdates:^{
[[NSOperationQueue currentQueue] addOperation:self.blockOperation];
} completion:nil];
}
}
Thanks.
Hi guys, can anyone tell whats the status of the bug http://openradar.appspot.com/12954582 ?
Do we still have to use reloadData
on insertion of first element to collection views in iOS 9+ ?
I was getting errors from the thread sanitizer that blocks were running off the main thread. Changed to use an array of raw blocks and calling them directly rather than running them through an NSBlockOperation.
https://gist.github.com/fdstevex/7a782bb864b7b23b8d8a8e2393286fac
Thanks very much for this, but could you explain the scope of blockOperation? It is created in controllerWillChangeContent: but used in other methods without reference to self, and I'm confused. Thanks.