From 3bb6b787a55e5407b218e4351f56f224b2a76a93 Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Mon, 15 Jul 2013 16:25:08 -0400 Subject: [PATCH 1/7] Don't return valid index path from filtered list if object is not found in source --- RZCollectionList/Classes/RZFilteredCollectionList.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RZCollectionList/Classes/RZFilteredCollectionList.m b/RZCollectionList/Classes/RZFilteredCollectionList.m index 3ebbb43..e82bb5e 100644 --- a/RZCollectionList/Classes/RZFilteredCollectionList.m +++ b/RZCollectionList/Classes/RZFilteredCollectionList.m @@ -428,7 +428,7 @@ - (id)objectAtIndexPath:(NSIndexPath*)indexPath - (NSIndexPath*)indexPathForObject:(id)object { NSIndexPath *sourceIndexPath = [self.sourceList indexPathForObject:object]; - return [self filteredIndexPathForSourceIndexPath:sourceIndexPath]; + return sourceIndexPath ? [self filteredIndexPathForSourceIndexPath:sourceIndexPath] : nil; } - (NSString *)sectionIndexTitleForSectionName:(NSString *)sectionName From ab6153d7fc6d56fd94ded4dd56fa07d38c9d13d9 Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Mon, 15 Jul 2013 17:42:19 -0400 Subject: [PATCH 2/7] Add cachedCopy method to section info protocol --- .../Classes/RZArrayCollectionList.h | 2 +- .../Classes/RZArrayCollectionList.m | 4 +-- .../Classes/RZCollectionListProtocol.h | 4 +++ .../Classes/RZCompositeCollectionList.m | 25 +++++++++++++++++++ .../Classes/RZFilteredCollectionList.m | 17 ++++++++++++- .../Classes/RZSortedCollectionList.m | 15 +++++++++-- 6 files changed, 61 insertions(+), 6 deletions(-) diff --git a/RZCollectionList/Classes/RZArrayCollectionList.h b/RZCollectionList/Classes/RZArrayCollectionList.h index 956d77b..b55cb11 100644 --- a/RZCollectionList/Classes/RZArrayCollectionList.h +++ b/RZCollectionList/Classes/RZArrayCollectionList.h @@ -10,7 +10,7 @@ #import "RZCollectionListProtocol.h" #import "RZBaseCollectionList.h" -@interface RZArrayCollectionListSectionInfo : NSObject +@interface RZArrayCollectionListSectionInfo : NSObject @property (nonatomic, assign) NSUInteger indexOffset; diff --git a/RZCollectionList/Classes/RZArrayCollectionList.m b/RZCollectionList/Classes/RZArrayCollectionList.m index 76650fd..2e364a0 100644 --- a/RZCollectionList/Classes/RZArrayCollectionList.m +++ b/RZCollectionList/Classes/RZArrayCollectionList.m @@ -321,7 +321,7 @@ - (void)beginUpdates // shallow copy sections self.sourceSectionsInfoBeforeUpdateShallow = [self.sectionsInfo copy]; - self.sourceSectionsInfoBeforeUpdateDeep = [[NSArray alloc] initWithArray:self.sectionsInfo copyItems:YES]; + self.sourceSectionsInfoBeforeUpdateDeep = [self.sectionsInfo valueForKey:@"cachedCopy"]; [self sendWillChangeContentNotifications]; } @@ -1000,7 +1000,7 @@ - (NSString*)description return [NSString stringWithFormat:@"%@ Name:%@ IndexTitle:%@ IndexOffset:%u NumberOfObjects:%u", [super description], self.name, self.indexTitle, self.indexOffset, self.numberOfObjects]; } -- (id)copyWithZone:(NSZone *)zone +- (id)cachedCopy { RZArrayCollectionListSectionInfo *copy = [[RZArrayCollectionListSectionInfo alloc] initWithName:self.name sectionIndexTitle:self.indexTitle numberOfObjects:self.numberOfObjects]; copy.indexOffset = self.indexOffset; diff --git a/RZCollectionList/Classes/RZCollectionListProtocol.h b/RZCollectionList/Classes/RZCollectionListProtocol.h index 7bef227..411b973 100644 --- a/RZCollectionList/Classes/RZCollectionListProtocol.h +++ b/RZCollectionList/Classes/RZCollectionListProtocol.h @@ -17,6 +17,10 @@ @property (nonatomic, assign, readonly) NSUInteger numberOfObjects; @property (nonatomic, readonly) NSArray *objects; +// distinct from copyWithZone in that all properties in a cached copy +// must return a static value (not derived from a source list) +- (id)cachedCopy; + @end @protocol RZCollectionListDelegate; diff --git a/RZCollectionList/Classes/RZCompositeCollectionList.m b/RZCollectionList/Classes/RZCompositeCollectionList.m index d782823..ae8c1cc 100644 --- a/RZCollectionList/Classes/RZCompositeCollectionList.m +++ b/RZCollectionList/Classes/RZCompositeCollectionList.m @@ -15,6 +15,11 @@ @interface RZCompositeCollectionListSectionInfo : NSObject )cachedCopy +{ + RZCompositeCollectionListSectionInfo *copy = [[RZCompositeCollectionListSectionInfo alloc] initWithName:[self.name copy] + sectionIndexTitle:[self.indexTitle copy] + compositeList:self.compositeList]; + + copy.objects = self.objects; + copy.numberOfObjects = self.numberOfObjects; + copy.isCachedCopy = YES; + return copy; +} + @end diff --git a/RZCollectionList/Classes/RZFilteredCollectionList.m b/RZCollectionList/Classes/RZFilteredCollectionList.m index e82bb5e..d88151c 100644 --- a/RZCollectionList/Classes/RZFilteredCollectionList.m +++ b/RZCollectionList/Classes/RZFilteredCollectionList.m @@ -17,6 +17,8 @@ @interface RZFilteredCollectionListSectionInfo : NSObject sourceSectionInfo; @property (nonatomic, weak) RZFilteredCollectionList *filteredList; +@property (nonatomic, assign) BOOL isCachedCopy; + - (id)initWithSourceSectionInfo:(id)sourceSectionInfo filteredList:(RZFilteredCollectionList*)filteredList; @end @@ -715,7 +717,7 @@ - (void)unfilterSourceObject:(id)object atSourceIndexPath:(NSIndexPath*)indexPat - (void)beginPotentialUpdates { self.contentChangeState = RZFilteredSourceListContentChangeStatePotentialChanges; - self.cachedSourceSections = [self.sourceList.sections copy]; + self.cachedSourceSections = [self.sourceList.sections valueForKey:@"cachedCopy"]; self.cachedSectionIndexes = [self.sectionIndexes copy]; self.cachedObjectIndexesForSectionShallow = [self.objectIndexesForSection copy]; self.cachedObjectIndexesForSectionDeep = [[NSMutableArray alloc] initWithArray:self.objectIndexesForSection copyItems:YES]; @@ -913,7 +915,20 @@ - (NSUInteger)numberOfObjects - (NSArray*)objects { + if (self.isCachedCopy) + { + return _objects; + } return [self.filteredList filteredObjectsForSection:self]; } +- (id)cachedCopy +{ + RZFilteredCollectionListSectionInfo *copy = [[RZFilteredCollectionListSectionInfo alloc] initWithSourceSectionInfo:[self.sourceSectionInfo cachedCopy] + filteredList:self.filteredList]; + copy.objects = self.objects; + copy.isCachedCopy = YES; + return copy; +} + @end diff --git a/RZCollectionList/Classes/RZSortedCollectionList.m b/RZCollectionList/Classes/RZSortedCollectionList.m index 63b6003..8e4cf6d 100644 --- a/RZCollectionList/Classes/RZSortedCollectionList.m +++ b/RZCollectionList/Classes/RZSortedCollectionList.m @@ -13,9 +13,12 @@ @interface RZSortedCollectionListSectionInfo : NSObject )cachedCopy { - RZSortedCollectionListSectionInfo *copy = [[RZSortedCollectionListSectionInfo alloc] initWithName:self.name sectionIndexTitle:self.indexTitle numberOfObjects:self.numberOfObjects]; + RZSortedCollectionListSectionInfo *copy = [[RZSortedCollectionListSectionInfo alloc] initWithName:[self.name copy] + sectionIndexTitle:[self.indexTitle copy] + numberOfObjects:self.numberOfObjects]; copy.indexOffset = self.indexOffset; copy.sortedList = self.sortedList; + copy.objects = self.objects; + copy.isCachedCopy = YES; return copy; } From bcfea16e1a446418150bb7ddc42b38d500de588e Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Mon, 15 Jul 2013 17:48:13 -0400 Subject: [PATCH 3/7] Update composite list to use cached section info for flat batch updates --- .../Classes/RZCollectionListProtocol.h | 8 +++++-- .../Classes/RZCompositeCollectionList.m | 24 +++++++------------ 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/RZCollectionList/Classes/RZCollectionListProtocol.h b/RZCollectionList/Classes/RZCollectionListProtocol.h index 411b973..93d781c 100644 --- a/RZCollectionList/Classes/RZCollectionListProtocol.h +++ b/RZCollectionList/Classes/RZCollectionListProtocol.h @@ -17,8 +17,12 @@ @property (nonatomic, assign, readonly) NSUInteger numberOfObjects; @property (nonatomic, readonly) NSArray *objects; -// distinct from copyWithZone in that all properties in a cached copy -// must return a static value (not derived from a source list) +//! Return a copy of this object which returns STATIC values for all properties above +/*! + This is different from NSCopying in the sense that a cachedCopy of section info + should not derive its property values from a dynamic source (source list, etc) + but rather should return a static value for each property. +*/ - (id)cachedCopy; @end diff --git a/RZCollectionList/Classes/RZCompositeCollectionList.m b/RZCollectionList/Classes/RZCompositeCollectionList.m index ae8c1cc..29ea591 100644 --- a/RZCollectionList/Classes/RZCompositeCollectionList.m +++ b/RZCollectionList/Classes/RZCompositeCollectionList.m @@ -39,7 +39,7 @@ @interface RZCompositeCollectionList () @property (nonatomic, strong) NSMutableArray *sourceListSectionRanges; @property (nonatomic, strong) NSMutableArray *sourceListForSection; @property (nonatomic, strong) NSArray *cachedSourceListSectionRanges; -@property (nonatomic, strong) NSArray *cachedSourceListSectionObjectCounts; // array of arrays, first dimension is source list, second dimension is # objs per section +@property (nonatomic, strong) NSArray *cachedSourceListSections; @property (nonatomic, assign) BOOL ignoreSections; @property (nonatomic, strong) RZCompositeCollectionListSectionInfo *singleSectionInfo; @@ -175,18 +175,18 @@ - (void)translateObjectNotification:(RZCollectionListObjectNotification *)notifi // find old index path based on original object counts if (nil != notification.indexPath) { - [self.cachedSourceListSectionObjectCounts enumerateObjectsUsingBlock:^(NSArray *objCounts, NSUInteger listIdx, BOOL *listStop) { + [self.cachedSourceListSections enumerateObjectsUsingBlock:^(NSArray *sourceSections, NSUInteger listIdx, BOOL *listStop) { if (listIdx == indexOfSourceList) { - [objCounts enumerateObjectsUsingBlock:^(NSNumber *objCount, NSUInteger sectionIdx, BOOL *sectionStop) { + [sourceSections enumerateObjectsUsingBlock:^(id sectionInfo, NSUInteger sectionIdx, BOOL *sectionStop) { if (sectionIdx == notification.indexPath.section) { *sectionStop = YES; } else { - oldRowOffset += [objCount unsignedIntegerValue]; + oldRowOffset += [sectionInfo numberOfObjects]; } }]; @@ -194,7 +194,7 @@ - (void)translateObjectNotification:(RZCollectionListObjectNotification *)notifi } else { - oldRowOffset += [[objCounts valueForKeyPath:@"@sum.self"] unsignedIntegerValue]; + oldRowOffset += [[sourceSections valueForKeyPath:@"@sum.numberOfObjects"] unsignedIntegerValue]; } }]; @@ -485,21 +485,15 @@ - (void)beginPotentialUpdates if (self.ignoreSections) { // keep track of initial number of objects in each section in each source list - NSMutableArray *sourceListSectionObjCounts = [NSMutableArray arrayWithCapacity:self.sourceLists.count]; + NSMutableArray *cachedSourceListSections = [NSMutableArray arrayWithCapacity:self.sourceLists.count]; [self.sourceLists enumerateObjectsUsingBlock:^(id sourceList, NSUInteger sourceListIdx, BOOL *stop) { NSArray *sourceSections = [sourceList sections]; - NSMutableArray *objCounts = [NSMutableArray arrayWithCapacity:sourceSections.count]; - - [sourceSections enumerateObjectsUsingBlock:^(id sectionInfo, NSUInteger sectionIdx, BOOL *stop) { - [objCounts addObject:[NSNumber numberWithUnsignedInteger:[sectionInfo numberOfObjects]]]; - }]; - - [sourceListSectionObjCounts addObject:objCounts]; + [cachedSourceListSections addObject:[sourceSections valueForKey:@"cachedCopy"]]; }]; - self.cachedSourceListSectionObjectCounts = sourceListSectionObjCounts; + self.cachedSourceListSections = cachedSourceListSections; } } @@ -526,7 +520,7 @@ - (void)endPotentialUpdates self.contentChangeState = RZCompositeSourceListContentChangeStateNoChanges; [self resetPendingNotifications]; self.cachedSourceListSectionRanges = nil; - self.cachedSourceListSectionObjectCounts = nil; + self.cachedSourceListSections = nil; } From 38c9b3cfd641fc135d319bd1ec2d4d35c2968c62 Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Tue, 16 Jul 2013 10:08:04 -0400 Subject: [PATCH 4/7] Section info wrapper class for fetched list --- .../Classes/RZArrayCollectionList.m | 10 +++ .../Classes/RZBaseCollectionList.h | 19 +++-- .../Classes/RZFetchedCollectionList.m | 69 ++++++++++++++++++- 3 files changed, 87 insertions(+), 11 deletions(-) diff --git a/RZCollectionList/Classes/RZArrayCollectionList.m b/RZCollectionList/Classes/RZArrayCollectionList.m index 2e364a0..696b0f5 100644 --- a/RZCollectionList/Classes/RZArrayCollectionList.m +++ b/RZCollectionList/Classes/RZArrayCollectionList.m @@ -14,10 +14,13 @@ @interface RZArrayCollectionListSectionInfo () @property (nonatomic, readwrite) NSString *name; @property (nonatomic, readwrite) NSString *indexTitle; +@property (nonatomic, strong, readwrite) NSArray *objects; @property (nonatomic, assign, readwrite) NSUInteger numberOfObjects; @property (nonatomic, weak) RZArrayCollectionList *arrayList; +@property (nonatomic, assign) BOOL isCachedCopy; + - (NSRange)range; @end @@ -987,6 +990,10 @@ - (NSString*)indexTitle - (NSArray*)objects { + if (self.isCachedCopy) + { + return _objects; + } return [self.arrayList.listObjects subarrayWithRange:NSMakeRange(self.indexOffset, self.numberOfObjects)]; } @@ -1003,7 +1010,10 @@ - (NSString*)description - (id)cachedCopy { RZArrayCollectionListSectionInfo *copy = [[RZArrayCollectionListSectionInfo alloc] initWithName:self.name sectionIndexTitle:self.indexTitle numberOfObjects:self.numberOfObjects]; + copy.arrayList = self.arrayList; copy.indexOffset = self.indexOffset; + copy.objects = self.objects; + copy.isCachedCopy = YES; return copy; } diff --git a/RZCollectionList/Classes/RZBaseCollectionList.h b/RZCollectionList/Classes/RZBaseCollectionList.h index a1596d4..438a3df 100644 --- a/RZCollectionList/Classes/RZBaseCollectionList.h +++ b/RZCollectionList/Classes/RZBaseCollectionList.h @@ -11,17 +11,16 @@ /*************************************************************** * - * Base class for providing common variables and - * utils for RZCollectionList "source" lists, i.e. - * lists that maintain/represent a concrete collection - * of objects, rather than a "modified" collection. - * - * Currently used as subclass for RZFetchedCollectionList - * and RZArrayCollectionList. + * Base class for providing common variables and + * utils for classes implementing the RZCollectionList protocol. * - * This class implements the protocol but requires subclasses - * to override the protocol methods, or an exception will be - * thrown. + * Classes implementing the protocol are not required to use this + * as a base class. + * + * This class implements the protocol itself, but requires + * subclasses to override the protocol methods, or a runtime + * exception will be thrown when an unimplemented method is + * called. * ****************************************************************/ diff --git a/RZCollectionList/Classes/RZFetchedCollectionList.m b/RZCollectionList/Classes/RZFetchedCollectionList.m index 9a7a949..888a537 100644 --- a/RZCollectionList/Classes/RZFetchedCollectionList.m +++ b/RZCollectionList/Classes/RZFetchedCollectionList.m @@ -10,6 +10,18 @@ #import "RZObserverCollection.h" #import "RZBaseCollectionList_Private.h" +@interface RZFetchedCollectionListSectionInfo : NSObject + +@property (nonatomic, strong, readwrite) NSArray *objects; + +@property (nonatomic, strong) id fetchedSectionInfo; +@property (nonatomic, assign) BOOL isCachedCopy; + +- (id)initWithFetchedResultsSectionInfo:(id)fetchedSectionInfo; + +@end + + @interface RZFetchedCollectionList () - (void)calculateCurrentIndexPathsForUpdates; @@ -67,7 +79,13 @@ - (NSArray*)listObjects - (NSArray*)sections { - return [self.controller sections]; + // convert to internal, cacheable section representation + NSArray *rawSections = [self.controller sections]; + NSMutableArray *sections = [NSMutableArray arrayWithCapacity:rawSections.count]; + [rawSections enumerateObjectsUsingBlock:^(id fetchedSection, NSUInteger idx, BOOL *stop) { + [sections addObject:[[RZFetchedCollectionListSectionInfo alloc] initWithFetchedResultsSectionInfo:fetchedSection]]; + }]; + return sections; } - (NSArray*)sectionIndexTitles @@ -163,3 +181,52 @@ - (NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTi } @end + +@implementation RZFetchedCollectionListSectionInfo + +- (id)initWithFetchedResultsSectionInfo:(id)fetchedSectionInfo +{ + if ((self = [super init])) + { + self.fetchedSectionInfo = fetchedSectionInfo; + } + return self; +} + +- (NSString*)name +{ + return [self.fetchedSectionInfo name]; +} + +- (NSString*)indexTitle +{ + return [self.fetchedSectionInfo indexTitle]; +} + +- (NSArray*)objects +{ + if (self.isCachedCopy) + { + return _objects; + } + return [self.fetchedSectionInfo objects]; +} + +- (NSUInteger)numberOfObjects +{ + if (self.isCachedCopy) + { + return [_objects count]; + } + return [self.fetchedSectionInfo numberOfObjects]; +} + +- (id)cachedCopy +{ + RZFetchedCollectionListSectionInfo *copy = [[RZFetchedCollectionListSectionInfo alloc] initWithFetchedResultsSectionInfo:self.fetchedSectionInfo]; + copy.objects = [self objects]; + copy.isCachedCopy = YES; + return copy; +} + +@end From 76001bda59c1fda158d728bda8f09e26d26f81a1 Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Tue, 16 Jul 2013 10:43:17 -0400 Subject: [PATCH 5/7] Cached section info implemented. A bit confusing but it works --- .../Classes/RZArrayCollectionList.m | 110 ++++++++---------- .../Classes/RZBaseCollectionList.m | 5 + .../Classes/RZCollectionListProtocol.h | 3 + .../Classes/RZCompositeCollectionList.m | 51 +++++--- .../Classes/RZFetchedCollectionList.m | 15 +++ .../Classes/RZFilteredCollectionList.m | 7 +- .../Classes/RZSortedCollectionList.m | 12 ++ 7 files changed, 128 insertions(+), 75 deletions(-) diff --git a/RZCollectionList/Classes/RZArrayCollectionList.m b/RZCollectionList/Classes/RZArrayCollectionList.m index 696b0f5..02f06f3 100644 --- a/RZCollectionList/Classes/RZArrayCollectionList.m +++ b/RZCollectionList/Classes/RZArrayCollectionList.m @@ -60,6 +60,10 @@ - (void)removeSectionAtIndex:(NSUInteger)index sendNotifications:(BOOL)shouldSen // Helpers for batch update - (NSIndexPath*)previousIndexPathForObject:(id)object; +// if not in a batch update, send necessary notifications, etc +- (void)prepareForUpdateIfNecessary; +- (void)finalizeUpdateIfNecessary; + - (void)sendDidChangeObjectNotification:(id)object atIndexPath:(NSIndexPath*)indexPath forChangeType:(RZCollectionListChangeType)type newIndexPath:(NSIndexPath*)newIndexPath; - (void)sendDidChangeSectionNotification:(id)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(RZCollectionListChangeType)type; @@ -196,17 +200,9 @@ - (void)addObject:(id)object toSection:(NSUInteger)section - (void)insertObject:(id)object atIndexPath:(NSIndexPath*)indexPath { - if (!self.batchUpdating) - { - [self sendWillChangeContentNotifications]; - } - + [self prepareForUpdateIfNecessary]; [self insertObject:object atIndexPath:indexPath sendNotifications:!self.batchUpdating]; - - if (!self.batchUpdating) - { - [self sendDidChangeContentNotifications]; - } + [self finalizeUpdateIfNecessary]; } - (void)removeObject:(id)object @@ -221,47 +217,23 @@ - (void)removeObject:(id)object - (void)removeObjectAtIndexPath:(NSIndexPath*)indexPath { - if (!self.batchUpdating) - { - [self sendWillChangeContentNotifications]; - } - + [self prepareForUpdateIfNecessary]; [self removeObjectAtIndexPath:indexPath sendNotifications:!self.batchUpdating]; - - if (!self.batchUpdating) - { - [self sendDidChangeContentNotifications]; - } + [self finalizeUpdateIfNecessary]; } - (void)replaceObjectAtIndexPath:(NSIndexPath*)indexPath withObject:(id)object { - if (!self.batchUpdating) - { - [self sendWillChangeContentNotifications]; - } - + [self prepareForUpdateIfNecessary]; [self replaceObjectAtIndexPath:indexPath withObject:object sendNotifications:!self.batchUpdating]; - - if (!self.batchUpdating) - { - [self sendDidChangeContentNotifications]; - } + [self finalizeUpdateIfNecessary]; } - (void)moveObjectAtIndexPath:(NSIndexPath*)sourceIndexPath toIndexPath:(NSIndexPath*)destinationIndexPath { - if (!self.batchUpdating) - { - [self sendWillChangeContentNotifications]; - } - + [self prepareForUpdateIfNecessary]; [self moveObjectAtIndexPath:sourceIndexPath toIndexPath:destinationIndexPath sendNotifications:!self.batchUpdating]; - - if (!self.batchUpdating) - { - [self sendDidChangeContentNotifications]; - } + [self finalizeUpdateIfNecessary]; } - (void)removeAllObjects @@ -280,17 +252,9 @@ - (void)addSection:(RZArrayCollectionListSectionInfo*)section - (void)insertSection:(RZArrayCollectionListSectionInfo*)section atIndex:(NSUInteger)index { - if (!self.batchUpdating) - { - [self sendWillChangeContentNotifications]; - } - + [self prepareForUpdateIfNecessary]; [self insertSection:section atIndex:index sendNotifications:!self.batchUpdating]; - - if (!self.batchUpdating) - { - [self sendDidChangeContentNotifications]; - } + [self finalizeUpdateIfNecessary]; } - (void)removeSection:(RZArrayCollectionListSectionInfo*)section @@ -302,17 +266,9 @@ - (void)removeSection:(RZArrayCollectionListSectionInfo*)section - (void)removeSectionAtIndex:(NSUInteger)index { - if (!self.batchUpdating) - { - [self sendWillChangeContentNotifications]; - } - + [self prepareForUpdateIfNecessary]; [self removeSectionAtIndex:index sendNotifications:!self.batchUpdating]; - - if (!self.batchUpdating) - { - [self sendDidChangeContentNotifications]; - } + [self finalizeUpdateIfNecessary]; } - (void)beginUpdates @@ -337,6 +293,11 @@ - (void)endUpdates [self processPendingChangeNotifications]; [self sendAllPendingChangeNotifications]; [self sendDidChangeContentNotifications]; + + self.sourceObjectsBeforeUpdate = nil; + self.sourceSectionsInfoBeforeUpdateShallow = nil; + self.sourceSectionsInfoBeforeUpdateDeep = nil; + self.batchUpdating = NO; } } @@ -621,6 +582,25 @@ - (NSIndexPath*)previousIndexPathForObject:(id)object #pragma mark - Notification Helpers +- (void)prepareForUpdateIfNecessary +{ + if (!self.batchUpdating) + { + // always need to have valid cached sections before sending willChange + self.sourceSectionsInfoBeforeUpdateDeep = [self.sectionsInfo valueForKey:@"cachedCopy"]; + [self sendWillChangeContentNotifications]; + } +} + +- (void)finalizeUpdateIfNecessary +{ + if (!self.batchUpdating) + { + [self sendDidChangeContentNotifications]; + self.sourceSectionsInfoBeforeUpdateDeep = nil; + } +} + - (void)sendDidChangeObjectNotification:(id)object atIndexPath:(NSIndexPath*)indexPath forChangeType:(RZCollectionListChangeType)type newIndexPath:(NSIndexPath*)newIndexPath { #if kRZCollectionListNotificationsLogging @@ -859,6 +839,16 @@ - (NSArray*)sections return [self.sectionsInfo copy]; } +- (NSArray*)cachedSections +{ + // if we aren't updating, just return regular sections + if (nil != self.sourceSectionsInfoBeforeUpdateDeep) + { + return [self.sourceSectionsInfoBeforeUpdateDeep copy]; + } + return self.sections; +} + - (NSArray*)sectionIndexTitles { NSMutableArray *indexTitles = [NSMutableArray arrayWithCapacity:self.sectionsInfo.count]; diff --git a/RZCollectionList/Classes/RZBaseCollectionList.m b/RZCollectionList/Classes/RZBaseCollectionList.m index 65c6f74..5cfcbd7 100644 --- a/RZCollectionList/Classes/RZBaseCollectionList.m +++ b/RZCollectionList/Classes/RZBaseCollectionList.m @@ -353,6 +353,11 @@ - (NSArray*)sections @throw [self missingProtocolMethodExceptionWithSelector:_cmd]; } +- (NSArray*)cachedSections +{ + @throw [self missingProtocolMethodExceptionWithSelector:_cmd]; +} + - (NSArray*)sectionIndexTitles { @throw [self missingProtocolMethodExceptionWithSelector:_cmd]; diff --git a/RZCollectionList/Classes/RZCollectionListProtocol.h b/RZCollectionList/Classes/RZCollectionListProtocol.h index 93d781c..26eb202 100644 --- a/RZCollectionList/Classes/RZCollectionListProtocol.h +++ b/RZCollectionList/Classes/RZCollectionListProtocol.h @@ -32,8 +32,11 @@ @protocol RZCollectionList +@required + @property (nonatomic, readonly) NSArray *listObjects; @property (nonatomic, readonly) NSArray *sections; +@property (nonatomic, readonly) NSArray *cachedSections; // sections cached prior to update, cleared when update is finished @property (nonatomic, readonly) NSArray *listObservers; @property (nonatomic, weak) id delegate; diff --git a/RZCollectionList/Classes/RZCompositeCollectionList.m b/RZCollectionList/Classes/RZCompositeCollectionList.m index 29ea591..c1e4340 100644 --- a/RZCollectionList/Classes/RZCompositeCollectionList.m +++ b/RZCollectionList/Classes/RZCompositeCollectionList.m @@ -43,6 +43,7 @@ @interface RZCompositeCollectionList () @property (nonatomic, assign) BOOL ignoreSections; @property (nonatomic, strong) RZCompositeCollectionListSectionInfo *singleSectionInfo; +@property (nonatomic, strong) RZCompositeCollectionListSectionInfo *cachedSingleSectionInfo; @property (nonatomic, assign) RZCompositeSourceListContentChangeState contentChangeState; - (void)configureSectionsWithSourceLists:(NSArray*)sourceLists; @@ -52,7 +53,7 @@ - (void)addSectionForSourceList:(id)sourceList; - (void)removeSectionForSourceList:(id)sourceList; // Update Helpers -- (void)beginPotentialUpdates; +- (void)beginPotentialUpdatesFromList:(id)updatingList; - (void)confirmPotentialUpdates; - (void)endPotentialUpdates; @@ -361,6 +362,29 @@ - (NSArray*)sections return sections; } +- (NSArray*)cachedSections +{ + NSArray *sections = nil; + if (self.ignoreSections) + { + sections = self.cachedSingleSectionInfo ? @[self.cachedSingleSectionInfo] : @[self.singleSectionInfo]; + } + else if (nil != self.cachedSourceListSections) + { + NSMutableArray *cachedSections = [NSMutableArray array]; + [self.cachedSourceListSections enumerateObjectsUsingBlock:^(NSArray *sectionsArray, NSUInteger idx, BOOL *stop) { + [cachedSections addObjectsFromArray:sectionsArray]; + }]; + sections = cachedSections; + } + else + { + sections = self.sections; + } + + return sections; +} + - (NSArray*)sectionIndexTitles { NSArray *sections = self.sections; @@ -478,23 +502,22 @@ - (NSInteger)sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)se #pragma mark - Update Helpers -- (void)beginPotentialUpdates +- (void)beginPotentialUpdatesFromList:(id)updatingList { self.contentChangeState = RZCompositeSourceListContentChangeStatePotentialChanges; self.cachedSourceListSectionRanges = [[NSArray alloc] initWithArray:self.sourceListSectionRanges copyItems:YES]; - if (self.ignoreSections) - { - // keep track of initial number of objects in each section in each source list - NSMutableArray *cachedSourceListSections = [NSMutableArray arrayWithCapacity:self.sourceLists.count]; - [self.sourceLists enumerateObjectsUsingBlock:^(id sourceList, NSUInteger sourceListIdx, BOOL *stop) { - - NSArray *sourceSections = [sourceList sections]; - [cachedSourceListSections addObject:[sourceSections valueForKey:@"cachedCopy"]]; - }]; + NSMutableArray *cachedSourceListSections = [NSMutableArray arrayWithCapacity:self.sourceLists.count]; + [self.sourceLists enumerateObjectsUsingBlock:^(id sourceList, NSUInteger sourceListIdx, BOOL *stop) { - self.cachedSourceListSections = cachedSourceListSections; - } + // don't care about cached sections if the list isn't updating + NSArray *sourceSections = (sourceList == updatingList) ? [sourceList cachedSections] : [sourceList sections]; + [cachedSourceListSections addObject:sourceSections]; + + }]; + self.cachedSourceListSections = cachedSourceListSections; + + self.cachedSingleSectionInfo = [self.singleSectionInfo cachedCopy]; } - (void)confirmPotentialUpdates @@ -544,7 +567,7 @@ - (void)collectionList:(id)collectionList didChangeSection:(id - (void)collectionListWillChangeContent:(id)collectionList { - [self beginPotentialUpdates]; + [self beginPotentialUpdatesFromList:collectionList]; } - (void)collectionListDidChangeContent:(id)collectionList diff --git a/RZCollectionList/Classes/RZFetchedCollectionList.m b/RZCollectionList/Classes/RZFetchedCollectionList.m index 888a537..7bb1c85 100644 --- a/RZCollectionList/Classes/RZFetchedCollectionList.m +++ b/RZCollectionList/Classes/RZFetchedCollectionList.m @@ -24,6 +24,8 @@ - (id)initWithFetchedResultsSectionInfo:(id)fetched @interface RZFetchedCollectionList () +@property (nonatomic, strong) NSArray *cachedFetchedSections; + - (void)calculateCurrentIndexPathsForUpdates; @end @@ -88,6 +90,16 @@ - (NSArray*)sections return sections; } +- (NSArray*)cachedSections +{ + // if we aren't updating, just return normal sections + if (nil != self.cachedFetchedSections) + { + return [self.cachedFetchedSections copy]; + } + return self.sections; +} + - (NSArray*)sectionIndexTitles { return [self.controller sectionIndexTitles]; @@ -148,6 +160,7 @@ - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller #if kRZCollectionListNotificationsLogging NSLog(@"RZFetchedCollectionList Will Change"); #endif + self.cachedFetchedSections = [self.sections valueForKey:@"cachedCopy"]; [self sendWillChangeContentNotifications]; } } @@ -167,6 +180,8 @@ - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller #endif // Send out DidChange Notifications [self sendDidChangeContentNotifications]; + + self.cachedFetchedSections = nil; } } diff --git a/RZCollectionList/Classes/RZFilteredCollectionList.m b/RZCollectionList/Classes/RZFilteredCollectionList.m index d88151c..f425edb 100644 --- a/RZCollectionList/Classes/RZFilteredCollectionList.m +++ b/RZCollectionList/Classes/RZFilteredCollectionList.m @@ -404,6 +404,11 @@ - (NSArray*)sections return [self filteredSections]; } +- (NSArray*)cachedSections +{ + return [self filteredCachedSections]; +} + - (NSArray*)sectionIndexTitles { NSArray *sections = self.sections; @@ -717,7 +722,7 @@ - (void)unfilterSourceObject:(id)object atSourceIndexPath:(NSIndexPath*)indexPat - (void)beginPotentialUpdates { self.contentChangeState = RZFilteredSourceListContentChangeStatePotentialChanges; - self.cachedSourceSections = [self.sourceList.sections valueForKey:@"cachedCopy"]; + self.cachedSourceSections = [self.sourceList cachedSections]; self.cachedSectionIndexes = [self.sectionIndexes copy]; self.cachedObjectIndexesForSectionShallow = [self.objectIndexesForSection copy]; self.cachedObjectIndexesForSectionDeep = [[NSMutableArray alloc] initWithArray:self.objectIndexesForSection copyItems:YES]; diff --git a/RZCollectionList/Classes/RZSortedCollectionList.m b/RZCollectionList/Classes/RZSortedCollectionList.m index 8e4cf6d..f3f2d8f 100644 --- a/RZCollectionList/Classes/RZSortedCollectionList.m +++ b/RZCollectionList/Classes/RZSortedCollectionList.m @@ -37,6 +37,7 @@ @interface RZSortedCollectionList () @property (nonatomic, strong) NSMutableArray *sortedListObjects; @property (nonatomic, strong) NSArray *cachedSortedListObjects; +@property (nonatomic, strong) NSArray *cachedSortedSections; @property (nonatomic, assign) RZSortedSourceListContentChangeState contentChangeState; @@ -127,6 +128,15 @@ - (NSArray*)sections return [self sortedSections]; } +- (NSArray*)cachedSections +{ + if (nil != self.cachedSortedSections) + { + return self.cachedSortedSections; + } + return self.sections; +} + - (NSArray*)sectionIndexTitles { NSArray *sections = self.sections; @@ -297,6 +307,7 @@ - (void)beginPotentialUpdates { self.contentChangeState = RZSortedSourceListContentChangeStatePotentialChanges; self.cachedSortedListObjects = [self.sortedListObjects copy]; + self.cachedSortedSections = [self.sortedSections valueForKey:@"cachedCopy"]; } - (void)confirmPotentialUpdates @@ -322,6 +333,7 @@ - (void)endPotentialUpdates [self resetPendingNotifications]; self.contentChangeState = RZSortedSourceListContentChangeStateNoChanges; self.cachedSortedListObjects = nil; + self.cachedSortedSections = nil; } - (void)processReceivedChangeNotifications From 998d70f5a523948a79a9f27450ad0e2b4f25df7f Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Tue, 16 Jul 2013 14:05:52 -0400 Subject: [PATCH 6/7] Fix bug with section info equality, implement isEqual for dynaically generated section info --- .../Classes/RZBaseCollectionList.m | 10 +++++-- .../Classes/RZCompositeCollectionList.m | 1 + .../Classes/RZFetchedCollectionList.m | 20 ++++++++++++++ .../Classes/RZFilteredCollectionList.m | 26 ++++++++++++++++++- 4 files changed, 54 insertions(+), 3 deletions(-) diff --git a/RZCollectionList/Classes/RZBaseCollectionList.m b/RZCollectionList/Classes/RZBaseCollectionList.m index 5cfcbd7..e415d19 100644 --- a/RZCollectionList/Classes/RZBaseCollectionList.m +++ b/RZCollectionList/Classes/RZBaseCollectionList.m @@ -178,7 +178,7 @@ - (void)cacheSectionNotificationWithSectionInfo:(id - (void)sendWillChangeContentNotifications { #if kRZCollectionListNotificationsLogging - NSLog(@"RZFilteredCollectionList Will Change"); + NSLog(@"%@ Will Change", self); #endif [[self.collectionListObservers allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj conformsToProtocol:@protocol(RZCollectionListObserver)]) @@ -191,7 +191,7 @@ - (void)sendWillChangeContentNotifications - (void)sendDidChangeContentNotifications { #if kRZCollectionListNotificationsLogging - NSLog(@"RZFilteredCollectionList Did Change"); + NSLog(@"%@ Did Change", self); #endif [[self.collectionListObservers allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj conformsToProtocol:@protocol(RZCollectionListObserver)]) @@ -288,6 +288,9 @@ - (void)sendSectionNotifications:(NSArray *)sectionNotifications [[self.collectionListObservers allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj conformsToProtocol:@protocol(RZCollectionListObserver)]) { +#if kRZCollectionListNotificationsLogging + NSLog(@"%@ Changed Section %@", self, notification); +#endif [obj collectionList:self didChangeSection:(id)notification.sectionInfo atIndex:notification.sectionIndex forChangeType:notification.type]; } }]; @@ -300,6 +303,9 @@ - (void)sendObjectNotifications:(NSArray *)objectNotifications [[self.collectionListObservers allObjects] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj conformsToProtocol:@protocol(RZCollectionListObserver)]) { +#if kRZCollectionListNotificationsLogging + NSLog(@"%@ Changed Object %@", self, notification); +#endif [obj collectionList:self didChangeObject:notification.object atIndexPath:notification.indexPath forChangeType:notification.type newIndexPath:notification.nuIndexPath]; } }]; diff --git a/RZCollectionList/Classes/RZCompositeCollectionList.m b/RZCollectionList/Classes/RZCompositeCollectionList.m index c1e4340..3b94c55 100644 --- a/RZCollectionList/Classes/RZCompositeCollectionList.m +++ b/RZCollectionList/Classes/RZCompositeCollectionList.m @@ -561,6 +561,7 @@ - (void)collectionList:(id)collectionList didChangeSection:(id // we don't care about section notifications if we are ignoring sections if (!self.ignoreSections) { + [self confirmPotentialUpdates]; [self cacheSectionNotificationWithSectionInfo:sectionInfo sectionIndex:sectionIndex type:type sourceList:collectionList]; } } diff --git a/RZCollectionList/Classes/RZFetchedCollectionList.m b/RZCollectionList/Classes/RZFetchedCollectionList.m index 7bb1c85..6b44a64 100644 --- a/RZCollectionList/Classes/RZFetchedCollectionList.m +++ b/RZCollectionList/Classes/RZFetchedCollectionList.m @@ -244,4 +244,24 @@ - (NSUInteger)numberOfObjects return copy; } +- (BOOL)isEqual:(id)object +{ + if ([object isKindOfClass:[RZFetchedCollectionListSectionInfo class]]) + { + return (self.fetchedSectionInfo == [object fetchedSectionInfo]) && (self.isCachedCopy == [object isCachedCopy]); + } + return NO; +} + +- (NSUInteger)hash +{ + // Might want to try to find a better hash for this... + return [[self.fetchedSectionInfo objects] hash] ^ self.numberOfObjects; +} + +- (NSString*)description +{ + return [NSString stringWithFormat:@"%@ number of objects: %d isCached: %@", [super description], self.numberOfObjects, self.isCachedCopy ? @"yes" : @"no"]; +} + @end diff --git a/RZCollectionList/Classes/RZFilteredCollectionList.m b/RZCollectionList/Classes/RZFilteredCollectionList.m index f425edb..93acdc6 100644 --- a/RZCollectionList/Classes/RZFilteredCollectionList.m +++ b/RZCollectionList/Classes/RZFilteredCollectionList.m @@ -406,7 +406,12 @@ - (NSArray*)sections - (NSArray*)cachedSections { - return [self filteredCachedSections]; + if (nil != self.cachedSourceSections) + { + return [self filteredCachedSections]; + } + return [self sections]; + } - (NSArray*)sectionIndexTitles @@ -936,4 +941,23 @@ - (NSArray*)objects return copy; } +- (BOOL)isEqual:(id)object +{ + if ([object isKindOfClass:[RZFilteredCollectionListSectionInfo class]]) + { + return [self.sourceSectionInfo isEqual:[object sourceSectionInfo]] && (self.cachedCopy == [object cachedCopy]); + } + return NO; +} + +- (NSUInteger)hash +{ + return [self.sourceSectionInfo hash] ^ [NSStringFromClass([self class]) hash]; +} + +- (NSString*)description +{ + return [NSString stringWithFormat:@"%@ number of objects: %d isCached: %@ source info: %@", [super description], self.numberOfObjects, self.isCachedCopy ? @"yes" : @"no", self.sourceSectionInfo]; +} + @end From 85f9b859ea67798b92e64cbd9c51681ee889b84b Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Thu, 25 Jul 2013 11:12:17 -0400 Subject: [PATCH 7/7] Fix array object update while not in batch mode - must deliver new index path so it works downstream --- RZCollectionList/Classes/RZArrayCollectionList.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RZCollectionList/Classes/RZArrayCollectionList.m b/RZCollectionList/Classes/RZArrayCollectionList.m index 02f06f3..4640b3e 100644 --- a/RZCollectionList/Classes/RZArrayCollectionList.m +++ b/RZCollectionList/Classes/RZArrayCollectionList.m @@ -716,7 +716,7 @@ - (void)objectUpdateNotificationReceived:(NSNotification*)notification { [self sendWillChangeContentNotifications]; - [self sendDidChangeObjectNotification:object atIndexPath:indexPath forChangeType:RZCollectionListChangeUpdate newIndexPath:nil]; + [self sendDidChangeObjectNotification:object atIndexPath:indexPath forChangeType:RZCollectionListChangeUpdate newIndexPath:indexPath]; [self sendDidChangeContentNotifications]; }