PSTCollectionViewData.m 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. //
  2. // PSTCollectionViewData.m
  3. // PSPDFKit
  4. //
  5. // Copyright (c) 2012-2013 Peter Steinberger. All rights reserved.
  6. //
  7. #import "PSTCollectionViewData.h"
  8. #import "PSTCollectionView.h"
  9. @interface PSTCollectionViewData () {
  10. CGRect _validLayoutRect;
  11. NSInteger _numItems;
  12. NSInteger _numSections;
  13. NSInteger *_sectionItemCounts;
  14. // id __strong* _globalItems; ///< _globalItems appears to be cached layoutAttributes. But adding that work in opens a can of worms, so deferring until later.
  15. /*
  16. // At this point, I've no idea how _screenPageDict is structured. Looks like some optimization for layoutAttributesForElementsInRect.
  17. And why UICGPointKey? Isn't that doable with NSValue?
  18. "<UICGPointKey: 0x11432d40>" = "<NSMutableIndexSet: 0x11432c60>[number of indexes: 9 (in 1 ranges), indexes: (0-8)]";
  19. "<UICGPointKey: 0xb94bf60>" = "<NSMutableIndexSet: 0x18dea7e0>[number of indexes: 11 (in 2 ranges), indexes: (6-15 17)]";
  20. (lldb) p (CGPoint)[[[[[collectionView valueForKey:@"_collectionViewData"] valueForKey:@"_screenPageDict"] allKeys] objectAtIndex:0] point]
  21. (CGPoint) $11 = (x=15, y=159)
  22. (lldb) p (CGPoint)[[[[[collectionView valueForKey:@"_collectionViewData"] valueForKey:@"_screenPageDict"] allKeys] objectAtIndex:1] point]
  23. (CGPoint) $12 = (x=15, y=1128)
  24. // https://github.com/steipete/iOS6-Runtime-Headers/blob/master/UICGPointKey.h
  25. NSMutableDictionary *_screenPageDict;
  26. */
  27. // @steipete
  28. CGSize _contentSize;
  29. struct {
  30. unsigned int contentSizeIsValid:1;
  31. unsigned int itemCountsAreValid:1;
  32. unsigned int layoutIsPrepared:1;
  33. }_collectionViewDataFlags;
  34. }
  35. @property (nonatomic, unsafe_unretained) PSTCollectionView *collectionView;
  36. @property (nonatomic, unsafe_unretained) PSTCollectionViewLayout *layout;
  37. @property (nonatomic, strong) NSArray *cachedLayoutAttributes;
  38. @end
  39. @implementation PSTCollectionViewData
  40. ///////////////////////////////////////////////////////////////////////////////////////////
  41. #pragma mark - NSObject
  42. - (id)initWithCollectionView:(PSTCollectionView *)collectionView layout:(PSTCollectionViewLayout *)layout {
  43. if ((self = [super init])) {
  44. _collectionView = collectionView;
  45. _layout = layout;
  46. }
  47. return self;
  48. }
  49. - (void)dealloc {
  50. free(_sectionItemCounts);
  51. }
  52. - (NSString *)description {
  53. return [NSString stringWithFormat:@"<%@: %p numItems:%ld numSections:%ld>", NSStringFromClass(self.class), self, (long)self.numberOfItems, (long)self.numberOfSections];
  54. }
  55. ///////////////////////////////////////////////////////////////////////////////////////////
  56. #pragma mark - Public
  57. - (void)invalidate {
  58. _collectionViewDataFlags.itemCountsAreValid = NO;
  59. _collectionViewDataFlags.layoutIsPrepared = NO;
  60. _validLayoutRect = CGRectNull; // don't set CGRectZero in case of _contentSize=CGSizeZero
  61. }
  62. - (CGRect)collectionViewContentRect {
  63. return (CGRect){.size=_contentSize};
  64. }
  65. - (void)validateLayoutInRect:(CGRect)rect {
  66. [self validateItemCounts];
  67. [self prepareToLoadData];
  68. // TODO: check if we need to fetch data from layout
  69. if (!CGRectEqualToRect(_validLayoutRect, rect)) {
  70. _validLayoutRect = rect;
  71. // we only want cell layoutAttributes & supplementaryView layoutAttributes
  72. self.cachedLayoutAttributes = [[self.layout layoutAttributesForElementsInRect:rect] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(PSTCollectionViewLayoutAttributes *evaluatedObject, NSDictionary *bindings) {
  73. return ([evaluatedObject isKindOfClass:PSTCollectionViewLayoutAttributes.class] &&
  74. ([evaluatedObject isCell] ||
  75. [evaluatedObject isSupplementaryView] ||
  76. [evaluatedObject isDecorationView]));
  77. }]];
  78. }
  79. }
  80. - (NSInteger)numberOfItems {
  81. [self validateItemCounts];
  82. return _numItems;
  83. }
  84. - (NSInteger)numberOfItemsBeforeSection:(NSInteger)section {
  85. [self validateItemCounts];
  86. NSAssert(section < _numSections, @"request for number of items in section %ld when there are only %ld sections in the collection view", (long)section, (long)_numSections);
  87. NSInteger returnCount = 0;
  88. for (int i = 0; i < section; i++) {
  89. returnCount += _sectionItemCounts[i];
  90. }
  91. return returnCount;
  92. }
  93. - (NSInteger)numberOfItemsInSection:(NSInteger)section {
  94. [self validateItemCounts];
  95. if (section >= _numSections || section < 0) {
  96. // In case of inconsistency returns the 'less harmful' amount of items. Throwing an exception here potentially
  97. // causes exceptions when data is consistent. Deleting sections is one of the parts sensitive to this.
  98. // All checks via assertions are done on CollectionView animation methods, specially 'endAnimations'.
  99. return 0;
  100. //@throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Section %d out of range: 0...%d", section, _numSections] userInfo:nil];
  101. }
  102. NSInteger numberOfItemsInSection = 0;
  103. if (_sectionItemCounts) {
  104. numberOfItemsInSection = _sectionItemCounts[section];
  105. }
  106. return numberOfItemsInSection;
  107. }
  108. - (NSInteger)numberOfSections {
  109. [self validateItemCounts];
  110. return _numSections;
  111. }
  112. - (CGRect)rectForItemAtIndexPath:(NSIndexPath *)indexPath {
  113. return CGRectZero;
  114. }
  115. - (NSIndexPath *)indexPathForItemAtGlobalIndex:(NSInteger)index {
  116. [self validateItemCounts];
  117. NSAssert(index < _numItems, @"request for index path for global index %ld when there are only %ld items in the collection view", (long)index, (long)_numItems);
  118. NSInteger section = 0;
  119. NSInteger countItems = 0;
  120. for (section = 0; section < _numSections; section++) {
  121. NSInteger countIncludingThisSection = countItems + _sectionItemCounts[section];
  122. if (countIncludingThisSection > index) break;
  123. countItems = countIncludingThisSection;
  124. }
  125. NSInteger item = index - countItems;
  126. return [NSIndexPath indexPathForItem:item inSection:section];
  127. }
  128. - (NSUInteger)globalIndexForItemAtIndexPath:(NSIndexPath *)indexPath {
  129. NSInteger offset = [self numberOfItemsBeforeSection:indexPath.section] + indexPath.item;
  130. return (NSUInteger)offset;
  131. }
  132. - (BOOL)layoutIsPrepared {
  133. return _collectionViewDataFlags.layoutIsPrepared;
  134. }
  135. - (void)setLayoutIsPrepared:(BOOL)layoutIsPrepared {
  136. _collectionViewDataFlags.layoutIsPrepared = (unsigned int)layoutIsPrepared;
  137. }
  138. ///////////////////////////////////////////////////////////////////////////////////////////
  139. #pragma mark - Fetch Layout Attributes
  140. - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
  141. [self validateLayoutInRect:rect];
  142. return self.cachedLayoutAttributes;
  143. }
  144. ///////////////////////////////////////////////////////////////////////////////////////////
  145. #pragma mark - Private
  146. // ensure item count is valid and loaded
  147. - (void)validateItemCounts {
  148. if (!_collectionViewDataFlags.itemCountsAreValid) {
  149. [self updateItemCounts];
  150. }
  151. }
  152. // query dataSource for new data
  153. - (void)updateItemCounts {
  154. // query how many sections there will be
  155. _numSections = 1;
  156. if ([self.collectionView.dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)]) {
  157. _numSections = [self.collectionView.dataSource numberOfSectionsInCollectionView:self.collectionView];
  158. }
  159. if (_numSections <= 0) { // early bail-out
  160. _numItems = 0;
  161. free(_sectionItemCounts);
  162. _sectionItemCounts = 0;
  163. _collectionViewDataFlags.itemCountsAreValid = YES;
  164. return;
  165. }
  166. // allocate space
  167. if (!_sectionItemCounts) {
  168. _sectionItemCounts = malloc((size_t)_numSections * sizeof(NSInteger));
  169. }else {
  170. _sectionItemCounts = realloc(_sectionItemCounts, (size_t)_numSections * sizeof(NSInteger));
  171. }
  172. // query cells per section
  173. _numItems = 0;
  174. for (NSInteger i = 0; i < _numSections; i++) {
  175. NSInteger cellCount = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:i];
  176. _sectionItemCounts[i] = cellCount;
  177. _numItems += cellCount;
  178. }
  179. _collectionViewDataFlags.itemCountsAreValid = YES;
  180. }
  181. - (void)prepareToLoadData {
  182. if (!self.layoutIsPrepared) {
  183. [self.layout prepareLayout];
  184. _contentSize = self.layout.collectionViewContentSize;
  185. self.layoutIsPrepared = YES;
  186. }
  187. }
  188. @end