PageRenderTime 55ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/TeamTalk/Pods/PSTCollectionView/PSTCollectionView/PSTCollectionView.m

https://gitlab.com/lisit1003/TTiOSClient
Objective C | 1121 lines | 873 code | 182 blank | 66 comment | 192 complexity | 927bd28ebf08449a91be2a0143da05bc MD5 | raw file
  1. //
  2. // PSTCollectionView.m
  3. // PSPDFKit
  4. //
  5. // Copyright (c) 2012-2013 Peter Steinberger. All rights reserved.
  6. //
  7. #import "PSTCollectionView.h"
  8. #import "PSTCollectionViewData.h"
  9. #import "PSTCollectionViewLayout+Internals.h"
  10. #import "PSTCollectionViewItemKey.h"
  11. #import <objc/runtime.h>
  12. #if TARGET_IPHONE_SIMULATOR
  13. #import <dlfcn.h>
  14. #endif
  15. @interface PSTCollectionViewLayout (Internal)
  16. @property (nonatomic, unsafe_unretained) PSTCollectionView *collectionView;
  17. @end
  18. @interface PSTCollectionViewData (Internal)
  19. - (void)prepareToLoadData;
  20. @end
  21. @interface PSTCollectionViewCell (Internal)
  22. - (void)performSelectionSegue;
  23. @end
  24. @interface PSTCollectionViewUpdateItem ()
  25. - (NSIndexPath *)indexPath;
  26. - (BOOL)isSectionOperation;
  27. @end
  28. @interface PSTCollectionViewLayoutAttributes () {
  29. char junk[128];
  30. }
  31. @property (nonatomic, copy) NSString *elementKind;
  32. @end
  33. CGFloat PSTSimulatorAnimationDragCoefficient(void);
  34. @class PSTCollectionViewExt;
  35. @interface PSTCollectionView () <UIScrollViewDelegate> {
  36. // ivar layout needs to EQUAL to UICollectionView.
  37. PSTCollectionViewLayout *_layout;
  38. __unsafe_unretained id<PSTCollectionViewDataSource> _dataSource;
  39. UIView *_backgroundView;
  40. NSMutableSet *_indexPathsForSelectedItems;
  41. NSMutableDictionary *_cellReuseQueues;
  42. NSMutableDictionary *_supplementaryViewReuseQueues;
  43. NSMutableDictionary *_decorationViewReuseQueues;
  44. NSMutableSet *_indexPathsForHighlightedItems;
  45. int _reloadingSuspendedCount;
  46. PSTCollectionReusableView *_firstResponderView;
  47. UIView *_newContentView;
  48. int _firstResponderViewType;
  49. NSString *_firstResponderViewKind;
  50. NSIndexPath *_firstResponderIndexPath;
  51. NSMutableDictionary *_allVisibleViewsDict;
  52. NSIndexPath *_pendingSelectionIndexPath;
  53. NSMutableSet *_pendingDeselectionIndexPaths;
  54. PSTCollectionViewData *_collectionViewData;
  55. id _update;
  56. CGRect _visibleBoundRects;
  57. CGRect _preRotationBounds;
  58. CGPoint _rotationBoundsOffset;
  59. int _rotationAnimationCount;
  60. int _updateCount;
  61. NSMutableArray *_insertItems;
  62. NSMutableArray *_deleteItems;
  63. NSMutableArray *_reloadItems;
  64. NSMutableArray *_moveItems;
  65. NSArray *_originalInsertItems;
  66. NSArray *_originalDeleteItems;
  67. UITouch *_currentTouch;
  68. void (^_updateCompletionHandler)(BOOL finished);
  69. NSMutableDictionary *_cellClassDict;
  70. NSMutableDictionary *_cellNibDict;
  71. NSMutableDictionary *_supplementaryViewClassDict;
  72. NSMutableDictionary *_supplementaryViewNibDict;
  73. NSMutableDictionary *_cellNibExternalObjectsTables;
  74. NSMutableDictionary *_supplementaryViewNibExternalObjectsTables;
  75. struct {
  76. unsigned int delegateShouldHighlightItemAtIndexPath : 1;
  77. unsigned int delegateDidHighlightItemAtIndexPath : 1;
  78. unsigned int delegateDidUnhighlightItemAtIndexPath : 1;
  79. unsigned int delegateShouldSelectItemAtIndexPath : 1;
  80. unsigned int delegateShouldDeselectItemAtIndexPath : 1;
  81. unsigned int delegateDidSelectItemAtIndexPath : 1;
  82. unsigned int delegateDidDeselectItemAtIndexPath : 1;
  83. unsigned int delegateSupportsMenus : 1;
  84. unsigned int delegateDidEndDisplayingCell : 1;
  85. unsigned int delegateDidEndDisplayingSupplementaryView : 1;
  86. unsigned int dataSourceNumberOfSections : 1;
  87. unsigned int dataSourceViewForSupplementaryElement : 1;
  88. unsigned int reloadSkippedDuringSuspension : 1;
  89. unsigned int scheduledUpdateVisibleCells : 1;
  90. unsigned int scheduledUpdateVisibleCellLayoutAttributes : 1;
  91. unsigned int allowsSelection : 1;
  92. unsigned int allowsMultipleSelection : 1;
  93. unsigned int updating : 1;
  94. unsigned int fadeCellsForBoundsChange : 1;
  95. unsigned int updatingLayout : 1;
  96. unsigned int needsReload : 1;
  97. unsigned int reloading : 1;
  98. unsigned int skipLayoutDuringSnapshotting : 1;
  99. unsigned int layoutInvalidatedSinceLastCellUpdate : 1;
  100. unsigned int doneFirstLayout : 1;
  101. }_collectionViewFlags;
  102. CGPoint _lastLayoutOffset;
  103. char filler[200]; // [HACK] Our class needs to be larger than Apple's class for the superclass change to work.
  104. }
  105. @property (nonatomic, strong) PSTCollectionViewData *collectionViewData;
  106. @property (nonatomic, strong, readonly) PSTCollectionViewExt *extVars;
  107. @property (nonatomic, readonly) id currentUpdate;
  108. @property (nonatomic, readonly) NSDictionary *visibleViewsDict;
  109. @property (nonatomic, assign) CGRect visibleBoundRects;
  110. @end
  111. // Used by PSTCollectionView for external variables.
  112. // (We need to keep the total class size equal to the UICollectionView variant)
  113. @interface PSTCollectionViewExt : NSObject
  114. @property (nonatomic, unsafe_unretained) id<PSTCollectionViewDelegate> collectionViewDelegate;
  115. @property (nonatomic, strong) PSTCollectionViewLayout *nibLayout;
  116. @property (nonatomic, strong) NSDictionary *nibCellsExternalObjects;
  117. @property (nonatomic, strong) NSDictionary *supplementaryViewsExternalObjects;
  118. @property (nonatomic, strong) NSIndexPath *touchingIndexPath;
  119. @property (nonatomic, strong) NSIndexPath *currentIndexPath;
  120. @end
  121. @implementation PSTCollectionViewExt
  122. @end
  123. const char kPSTColletionViewExt;
  124. @implementation PSTCollectionView
  125. @synthesize collectionViewLayout = _layout;
  126. @synthesize currentUpdate = _update;
  127. @synthesize visibleViewsDict = _allVisibleViewsDict;
  128. ///////////////////////////////////////////////////////////////////////////////////////////
  129. #pragma mark - NSObject
  130. static void PSTCollectionViewCommonSetup(PSTCollectionView *_self) {
  131. _self.allowsSelection = YES;
  132. _self->_indexPathsForSelectedItems = [NSMutableSet new];
  133. _self->_indexPathsForHighlightedItems = [NSMutableSet new];
  134. _self->_cellReuseQueues = [NSMutableDictionary new];
  135. _self->_supplementaryViewReuseQueues = [NSMutableDictionary new];
  136. _self->_decorationViewReuseQueues = [NSMutableDictionary new];
  137. _self->_allVisibleViewsDict = [NSMutableDictionary new];
  138. _self->_cellClassDict = [NSMutableDictionary new];
  139. _self->_cellNibDict = [NSMutableDictionary new];
  140. _self->_supplementaryViewClassDict = [NSMutableDictionary new];
  141. _self->_supplementaryViewNibDict = [NSMutableDictionary new];
  142. // add class that saves additional ivars
  143. objc_setAssociatedObject(_self, &kPSTColletionViewExt, [PSTCollectionViewExt new], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  144. }
  145. - (id)initWithFrame:(CGRect)frame {
  146. return [self initWithFrame:frame collectionViewLayout:nil];
  147. }
  148. - (id)initWithFrame:(CGRect)frame collectionViewLayout:(PSTCollectionViewLayout *)layout {
  149. if ((self = [super initWithFrame:frame])) {
  150. // Set self as the UIScrollView's delegate
  151. [super setDelegate:self];
  152. PSTCollectionViewCommonSetup(self);
  153. self.collectionViewLayout = layout;
  154. _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
  155. }
  156. return self;
  157. }
  158. - (id)initWithCoder:(NSCoder *)inCoder {
  159. if ((self = [super initWithCoder:inCoder])) {
  160. // Set self as the UIScrollView's delegate
  161. [super setDelegate:self];
  162. PSTCollectionViewCommonSetup(self);
  163. self.extVars.nibLayout = [inCoder decodeObjectForKey:@"UICollectionLayout"];
  164. NSDictionary *cellExternalObjects = [inCoder decodeObjectForKey:@"UICollectionViewCellPrototypeNibExternalObjects"];
  165. NSDictionary *cellNibs = [inCoder decodeObjectForKey:@"UICollectionViewCellNibDict"];
  166. for (NSString *identifier in cellNibs.allKeys) {
  167. _cellNibDict[identifier] = cellNibs[identifier];
  168. }
  169. self.extVars.nibCellsExternalObjects = cellExternalObjects;
  170. NSDictionary *supplementaryViewExternalObjects = [inCoder decodeObjectForKey:@"UICollectionViewSupplementaryViewPrototypeNibExternalObjects"];
  171. NSDictionary *supplementaryViewNibs = [inCoder decodeObjectForKey:@"UICollectionViewSupplementaryViewNibDict"];
  172. for (NSString *identifier in supplementaryViewNibs.allKeys) {
  173. _supplementaryViewNibDict[identifier] = supplementaryViewNibs[identifier];
  174. }
  175. self.extVars.supplementaryViewsExternalObjects = supplementaryViewExternalObjects;
  176. }
  177. return self;
  178. }
  179. - (void)awakeFromNib {
  180. [super awakeFromNib];
  181. PSTCollectionViewLayout *nibLayout = self.extVars.nibLayout;
  182. if (nibLayout) {
  183. self.collectionViewLayout = nibLayout;
  184. self.extVars.nibLayout = nil;
  185. }
  186. }
  187. - (NSString *)description {
  188. return [NSString stringWithFormat:@"%@ collection view layout: %@", [super description], self.collectionViewLayout];
  189. }
  190. ///////////////////////////////////////////////////////////////////////////////////////////
  191. #pragma mark - UIView
  192. - (void)layoutSubviews {
  193. [super layoutSubviews];
  194. // Adding alpha animation to make the relayouting smooth
  195. if (_collectionViewFlags.fadeCellsForBoundsChange) {
  196. CATransition *transition = [CATransition animation];
  197. transition.duration = 0.25f * PSTSimulatorAnimationDragCoefficient();
  198. transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  199. transition.type = kCATransitionFade;
  200. [self.layer addAnimation:transition forKey:@"rotationAnimation"];
  201. }
  202. [_collectionViewData validateLayoutInRect:self.bounds];
  203. // update cells
  204. if (_collectionViewFlags.fadeCellsForBoundsChange) {
  205. [CATransaction begin];
  206. [CATransaction setDisableActions:YES];
  207. }
  208. if (!_collectionViewFlags.updatingLayout)
  209. [self updateVisibleCellsNow:YES];
  210. if (_collectionViewFlags.fadeCellsForBoundsChange) {
  211. [CATransaction commit];
  212. }
  213. // do we need to update contentSize?
  214. CGSize contentSize = [_collectionViewData collectionViewContentRect].size;
  215. if (!CGSizeEqualToSize(self.contentSize, contentSize)) {
  216. self.contentSize = contentSize;
  217. // if contentSize is different, we need to re-evaluate layout, bounds (contentOffset) might changed
  218. [_collectionViewData validateLayoutInRect:self.bounds];
  219. [self updateVisibleCellsNow:YES];
  220. }
  221. if (_backgroundView) {
  222. _backgroundView.frame = (CGRect){.origin=self.contentOffset, .size=self.bounds.size};
  223. }
  224. _collectionViewFlags.fadeCellsForBoundsChange = NO;
  225. _collectionViewFlags.doneFirstLayout = YES;
  226. }
  227. - (void)setFrame:(CGRect)frame {
  228. if (!CGRectEqualToRect(frame, self.frame)) {
  229. CGRect bounds = (CGRect){.origin=self.contentOffset, .size=frame.size};
  230. BOOL shouldInvalidate = [self.collectionViewLayout shouldInvalidateLayoutForBoundsChange:bounds];
  231. [super setFrame:frame];
  232. if (shouldInvalidate) {
  233. [self invalidateLayout];
  234. _collectionViewFlags.fadeCellsForBoundsChange = YES;
  235. }
  236. }
  237. }
  238. - (void)setBounds:(CGRect)bounds {
  239. if (!CGRectEqualToRect(bounds, self.bounds)) {
  240. BOOL shouldInvalidate = [self.collectionViewLayout shouldInvalidateLayoutForBoundsChange:bounds];
  241. [super setBounds:bounds];
  242. if (shouldInvalidate) {
  243. [self invalidateLayout];
  244. _collectionViewFlags.fadeCellsForBoundsChange = YES;
  245. }
  246. }
  247. }
  248. ///////////////////////////////////////////////////////////////////////////////////////////
  249. #pragma mark - UIScrollViewDelegate
  250. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  251. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  252. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
  253. [delegate scrollViewDidScroll:scrollView];
  254. }
  255. }
  256. - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  257. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  258. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidZoom:)]) {
  259. [delegate scrollViewDidZoom:scrollView];
  260. }
  261. }
  262. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  263. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  264. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
  265. [delegate scrollViewWillBeginDragging:scrollView];
  266. }
  267. }
  268. - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
  269. // Let collectionViewLayout decide where to stop.
  270. *targetContentOffset = [[self collectionViewLayout] targetContentOffsetForProposedContentOffset:*targetContentOffset withScrollingVelocity:velocity];
  271. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  272. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
  273. //if collectionViewDelegate implements this method, it may modify targetContentOffset as well
  274. [delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
  275. }
  276. }
  277. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  278. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  279. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) {
  280. [delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
  281. }
  282. // if we are in the middle of a cell touch event, perform the "touchEnded" simulation
  283. if (self.extVars.touchingIndexPath) {
  284. [self cellTouchCancelled];
  285. }
  286. }
  287. - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
  288. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  289. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)]) {
  290. [delegate scrollViewWillBeginDecelerating:scrollView];
  291. }
  292. }
  293. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  294. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  295. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) {
  296. [delegate scrollViewDidEndDecelerating:scrollView];
  297. }
  298. }
  299. - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
  300. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  301. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) {
  302. [delegate scrollViewDidEndScrollingAnimation:scrollView];
  303. }
  304. }
  305. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  306. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  307. if ((id)delegate != self && [delegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
  308. return [delegate viewForZoomingInScrollView:scrollView];
  309. }
  310. return nil;
  311. }
  312. - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
  313. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  314. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
  315. [delegate scrollViewWillBeginZooming:scrollView withView:view];
  316. }
  317. }
  318. - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
  319. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  320. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)]) {
  321. [delegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
  322. }
  323. }
  324. - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
  325. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  326. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) {
  327. return [delegate scrollViewShouldScrollToTop:scrollView];
  328. }
  329. return YES;
  330. }
  331. - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
  332. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  333. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidScrollToTop:)]) {
  334. [delegate scrollViewDidScrollToTop:scrollView];
  335. }
  336. }
  337. ///////////////////////////////////////////////////////////////////////////////////////////
  338. #pragma mark - Public
  339. - (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier {
  340. NSParameterAssert(cellClass);
  341. NSParameterAssert(identifier);
  342. _cellClassDict[identifier] = cellClass;
  343. }
  344. - (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier {
  345. NSParameterAssert(viewClass);
  346. NSParameterAssert(elementKind);
  347. NSParameterAssert(identifier);
  348. NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", elementKind, identifier];
  349. _supplementaryViewClassDict[kindAndIdentifier] = viewClass;
  350. }
  351. - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier {
  352. NSArray *topLevelObjects = [nib instantiateWithOwner:nil options:nil];
  353. #pragma unused(topLevelObjects)
  354. NSAssert(topLevelObjects.count == 1 && [topLevelObjects[0] isKindOfClass:PSTCollectionViewCell.class], @"must contain exactly 1 top level object which is a PSTCollectionViewCell");
  355. _cellNibDict[identifier] = nib;
  356. }
  357. - (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier {
  358. NSArray *topLevelObjects = [nib instantiateWithOwner:nil options:nil];
  359. #pragma unused(topLevelObjects)
  360. NSAssert(topLevelObjects.count == 1 && [topLevelObjects[0] isKindOfClass:PSTCollectionReusableView.class], @"must contain exactly 1 top level object which is a PSTCollectionReusableView");
  361. NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", kind, identifier];
  362. _supplementaryViewNibDict[kindAndIdentifier] = nib;
  363. }
  364. - (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
  365. // de-queue cell (if available)
  366. NSMutableArray *reusableCells = _cellReuseQueues[identifier];
  367. PSTCollectionViewCell *cell = [reusableCells lastObject];
  368. PSTCollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
  369. if (cell) {
  370. [reusableCells removeObjectAtIndex:reusableCells.count - 1];
  371. }else {
  372. if (_cellNibDict[identifier]) {
  373. // Cell was registered via registerNib:forCellWithReuseIdentifier:
  374. UINib *cellNib = _cellNibDict[identifier];
  375. NSDictionary *externalObjects = self.extVars.nibCellsExternalObjects[identifier];
  376. if (externalObjects) {
  377. cell = [cellNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
  378. }else {
  379. cell = [cellNib instantiateWithOwner:self options:nil][0];
  380. }
  381. }else {
  382. Class cellClass = _cellClassDict[identifier];
  383. // compatibility layer
  384. Class collectionViewCellClass = NSClassFromString(@"UICollectionViewCell");
  385. if (collectionViewCellClass && [cellClass isEqual:collectionViewCellClass]) {
  386. cellClass = PSTCollectionViewCell.class;
  387. }
  388. if (cellClass == nil) {
  389. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for identifier %@", identifier] userInfo:nil];
  390. }
  391. if (attributes) {
  392. cell = [[cellClass alloc] initWithFrame:attributes.frame];
  393. }else {
  394. cell = [cellClass new];
  395. }
  396. }
  397. cell.collectionView = self;
  398. cell.reuseIdentifier = identifier;
  399. }
  400. [cell applyLayoutAttributes:attributes];
  401. return cell;
  402. }
  403. - (id)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
  404. NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", elementKind, identifier];
  405. NSMutableArray *reusableViews = _supplementaryViewReuseQueues[kindAndIdentifier];
  406. PSTCollectionReusableView *view = [reusableViews lastObject];
  407. if (view) {
  408. [reusableViews removeObjectAtIndex:reusableViews.count - 1];
  409. }else {
  410. if (_supplementaryViewNibDict[kindAndIdentifier]) {
  411. // supplementary view was registered via registerNib:forCellWithReuseIdentifier:
  412. UINib *supplementaryViewNib = _supplementaryViewNibDict[kindAndIdentifier];
  413. NSDictionary *externalObjects = self.extVars.supplementaryViewsExternalObjects[kindAndIdentifier];
  414. if (externalObjects) {
  415. view = [supplementaryViewNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
  416. }else {
  417. view = [supplementaryViewNib instantiateWithOwner:self options:nil][0];
  418. }
  419. }else {
  420. Class viewClass = _supplementaryViewClassDict[kindAndIdentifier];
  421. Class reusableViewClass = NSClassFromString(@"UICollectionReusableView");
  422. if (reusableViewClass && [viewClass isEqual:reusableViewClass]) {
  423. viewClass = PSTCollectionReusableView.class;
  424. }
  425. if (viewClass == nil) {
  426. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for kind/identifier %@", kindAndIdentifier] userInfo:nil];
  427. }
  428. if (self.collectionViewLayout) {
  429. PSTCollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath];
  430. if (attributes) {
  431. view = [[viewClass alloc] initWithFrame:attributes.frame];
  432. }
  433. }else {
  434. view = [viewClass new];
  435. }
  436. }
  437. view.collectionView = self;
  438. view.reuseIdentifier = identifier;
  439. }
  440. return view;
  441. }
  442. - (id)dequeueReusableOrCreateDecorationViewOfKind:(NSString *)elementKind forIndexPath:(NSIndexPath *)indexPath {
  443. NSMutableArray *reusableViews = _decorationViewReuseQueues[elementKind];
  444. PSTCollectionReusableView *view = [reusableViews lastObject];
  445. PSTCollectionViewLayout *collectionViewLayout = self.collectionViewLayout;
  446. PSTCollectionViewLayoutAttributes *attributes = [collectionViewLayout layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:indexPath];
  447. if (view) {
  448. [reusableViews removeObjectAtIndex:reusableViews.count - 1];
  449. }else {
  450. NSDictionary *decorationViewNibDict = collectionViewLayout.decorationViewNibDict;
  451. NSDictionary *decorationViewExternalObjects = collectionViewLayout.decorationViewExternalObjectsTables;
  452. if (decorationViewNibDict[elementKind]) {
  453. // supplementary view was registered via registerNib:forCellWithReuseIdentifier:
  454. UINib *supplementaryViewNib = decorationViewNibDict[elementKind];
  455. NSDictionary *externalObjects = decorationViewExternalObjects[elementKind];
  456. if (externalObjects) {
  457. view = [supplementaryViewNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
  458. }else {
  459. view = [supplementaryViewNib instantiateWithOwner:self options:nil][0];
  460. }
  461. }else {
  462. NSDictionary *decorationViewClassDict = collectionViewLayout.decorationViewClassDict;
  463. Class viewClass = decorationViewClassDict[elementKind];
  464. Class reusableViewClass = NSClassFromString(@"UICollectionReusableView");
  465. if (reusableViewClass && [viewClass isEqual:reusableViewClass]) {
  466. viewClass = PSTCollectionReusableView.class;
  467. }
  468. if (viewClass == nil) {
  469. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for identifier %@", elementKind] userInfo:nil];
  470. }
  471. if (attributes) {
  472. view = [[viewClass alloc] initWithFrame:attributes.frame];
  473. }else {
  474. view = [viewClass new];
  475. }
  476. }
  477. view.collectionView = self;
  478. view.reuseIdentifier = elementKind;
  479. }
  480. [view applyLayoutAttributes:attributes];
  481. return view;
  482. }
  483. - (NSArray *)allCells {
  484. return [[_allVisibleViewsDict allValues] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  485. return [evaluatedObject isKindOfClass:PSTCollectionViewCell.class];
  486. }]];
  487. }
  488. - (NSArray *)visibleCells {
  489. return [[_allVisibleViewsDict allValues] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  490. return [evaluatedObject isKindOfClass:PSTCollectionViewCell.class] && CGRectIntersectsRect(self.bounds, [evaluatedObject frame]);
  491. }]];
  492. }
  493. - (void)reloadData {
  494. if (_reloadingSuspendedCount != 0) return;
  495. [self invalidateLayout];
  496. [_allVisibleViewsDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  497. if ([obj isKindOfClass:UIView.class]) {
  498. [obj removeFromSuperview];
  499. }
  500. }];
  501. [_allVisibleViewsDict removeAllObjects];
  502. for (NSIndexPath *indexPath in _indexPathsForSelectedItems) {
  503. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
  504. selectedCell.selected = NO;
  505. selectedCell.highlighted = NO;
  506. }
  507. [_indexPathsForSelectedItems removeAllObjects];
  508. [_indexPathsForHighlightedItems removeAllObjects];
  509. [self setNeedsLayout];
  510. }
  511. ///////////////////////////////////////////////////////////////////////////////////////////
  512. #pragma mark - Query Grid
  513. - (NSInteger)numberOfSections {
  514. return [_collectionViewData numberOfSections];
  515. }
  516. - (NSInteger)numberOfItemsInSection:(NSInteger)section {
  517. return [_collectionViewData numberOfItemsInSection:section];
  518. }
  519. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
  520. return [[self collectionViewLayout] layoutAttributesForItemAtIndexPath:indexPath];
  521. }
  522. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
  523. return [[self collectionViewLayout] layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
  524. }
  525. - (NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point {
  526. PSTCollectionViewLayoutAttributes *attributes = [[self.collectionViewLayout layoutAttributesForElementsInRect:CGRectMake(point.x, point.y, 1, 1)] lastObject];
  527. return attributes.indexPath;
  528. }
  529. - (NSIndexPath *)indexPathForCell:(PSTCollectionViewCell *)cell {
  530. __block NSIndexPath *indexPath = nil;
  531. [_allVisibleViewsDict enumerateKeysAndObjectsWithOptions:kNilOptions usingBlock:^(id key, id obj, BOOL *stop) {
  532. PSTCollectionViewItemKey *itemKey = (PSTCollectionViewItemKey *)key;
  533. if (itemKey.type == PSTCollectionViewItemTypeCell) {
  534. PSTCollectionViewCell *currentCell = (PSTCollectionViewCell *)obj;
  535. if (currentCell == cell) {
  536. indexPath = itemKey.indexPath;
  537. *stop = YES;
  538. }
  539. }
  540. }];
  541. return indexPath;
  542. }
  543. - (PSTCollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  544. // NSInteger index = [_collectionViewData globalIndexForItemAtIndexPath:indexPath];
  545. // TODO Apple uses some kind of globalIndex for this.
  546. __block PSTCollectionViewCell *cell = nil;
  547. [_allVisibleViewsDict enumerateKeysAndObjectsWithOptions:0 usingBlock:^(id key, id obj, BOOL *stop) {
  548. PSTCollectionViewItemKey *itemKey = (PSTCollectionViewItemKey *)key;
  549. if (itemKey.type == PSTCollectionViewItemTypeCell) {
  550. if ([itemKey.indexPath isEqual:indexPath]) {
  551. cell = obj;
  552. *stop = YES;
  553. }
  554. }
  555. }];
  556. return cell;
  557. }
  558. - (NSArray *)indexPathsForVisibleItems {
  559. NSArray *visibleCells = self.visibleCells;
  560. NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:visibleCells.count];
  561. [visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  562. PSTCollectionViewCell *cell = (PSTCollectionViewCell *)obj;
  563. [indexPaths addObject:cell.layoutAttributes.indexPath];
  564. }];
  565. return indexPaths;
  566. }
  567. // returns nil or an array of selected index paths
  568. - (NSArray *)indexPathsForSelectedItems {
  569. return [_indexPathsForSelectedItems allObjects];
  570. }
  571. // Interacting with the collection view.
  572. - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(PSTCollectionViewScrollPosition)scrollPosition animated:(BOOL)animated {
  573. // Ensure grid is laid out; else we can't scroll.
  574. [self layoutSubviews];
  575. PSTCollectionViewLayoutAttributes *layoutAttributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
  576. if (layoutAttributes) {
  577. CGRect targetRect = [self makeRect:layoutAttributes.frame toScrollPosition:scrollPosition];
  578. [self scrollRectToVisible:targetRect animated:animated];
  579. }
  580. }
  581. - (CGRect)makeRect:(CGRect)targetRect toScrollPosition:(PSTCollectionViewScrollPosition)scrollPosition {
  582. // split parameters
  583. NSUInteger verticalPosition = scrollPosition&0x07; // 0000 0111
  584. NSUInteger horizontalPosition = scrollPosition&0x38; // 0011 1000
  585. if (verticalPosition != PSTCollectionViewScrollPositionNone
  586. && verticalPosition != PSTCollectionViewScrollPositionTop
  587. && verticalPosition != PSTCollectionViewScrollPositionCenteredVertically
  588. && verticalPosition != PSTCollectionViewScrollPositionBottom) {
  589. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"PSTCollectionViewScrollPosition: attempt to use a scroll position with multiple vertical positioning styles" userInfo:nil];
  590. }
  591. if (horizontalPosition != PSTCollectionViewScrollPositionNone
  592. && horizontalPosition != PSTCollectionViewScrollPositionLeft
  593. && horizontalPosition != PSTCollectionViewScrollPositionCenteredHorizontally
  594. && horizontalPosition != PSTCollectionViewScrollPositionRight) {
  595. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"PSTCollectionViewScrollPosition: attempt to use a scroll position with multiple horizontal positioning styles" userInfo:nil];
  596. }
  597. CGRect frame = self.layer.bounds;
  598. CGFloat calculateX;
  599. CGFloat calculateY;
  600. switch (verticalPosition) {
  601. case PSTCollectionViewScrollPositionCenteredVertically:
  602. calculateY = fmax(targetRect.origin.y - ((frame.size.height / 2) - (targetRect.size.height / 2)), -self.contentInset.top);
  603. targetRect = CGRectMake(targetRect.origin.x, calculateY, targetRect.size.width, frame.size.height);
  604. break;
  605. case PSTCollectionViewScrollPositionTop:
  606. targetRect = CGRectMake(targetRect.origin.x, targetRect.origin.y, targetRect.size.width, frame.size.height);
  607. break;
  608. case PSTCollectionViewScrollPositionBottom:
  609. calculateY = fmax(targetRect.origin.y - (frame.size.height - targetRect.size.height), -self.contentInset.top);
  610. targetRect = CGRectMake(targetRect.origin.x, calculateY, targetRect.size.width, frame.size.height);
  611. break;
  612. }
  613. switch (horizontalPosition) {
  614. case PSTCollectionViewScrollPositionCenteredHorizontally:
  615. calculateX = targetRect.origin.x - ((frame.size.width / 2) - (targetRect.size.width / 2));
  616. targetRect = CGRectMake(calculateX, targetRect.origin.y, frame.size.width, targetRect.size.height);
  617. break;
  618. case PSTCollectionViewScrollPositionLeft:
  619. targetRect = CGRectMake(targetRect.origin.x, targetRect.origin.y, frame.size.width, targetRect.size.height);
  620. break;
  621. case PSTCollectionViewScrollPositionRight:
  622. calculateX = targetRect.origin.x - (frame.size.width - targetRect.size.width);
  623. targetRect = CGRectMake(calculateX, targetRect.origin.y, frame.size.width, targetRect.size.height);
  624. break;
  625. }
  626. return targetRect;
  627. }
  628. ///////////////////////////////////////////////////////////////////////////////////////////
  629. #pragma mark - Touch Handling
  630. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  631. [super touchesBegan:touches withEvent:event];
  632. // reset touching state vars
  633. self.extVars.touchingIndexPath = nil;
  634. self.extVars.currentIndexPath = nil;
  635. CGPoint touchPoint = [[touches anyObject] locationInView:self];
  636. NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
  637. if (indexPath && self.allowsSelection) {
  638. if (![self highlightItemAtIndexPath:indexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES])
  639. return;
  640. self.extVars.touchingIndexPath = indexPath;
  641. self.extVars.currentIndexPath = indexPath;
  642. if (!self.allowsMultipleSelection) {
  643. // temporally unhighlight background on touchesBegan (keeps selected by _indexPathsForSelectedItems)
  644. // single-select only mode only though
  645. NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
  646. if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:self.extVars.touchingIndexPath]) {
  647. // iOS6 UICollectionView deselects cell without notification
  648. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:tempDeselectIndexPath];
  649. selectedCell.selected = NO;
  650. }
  651. }
  652. }
  653. }
  654. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  655. [super touchesMoved:touches withEvent:event];
  656. // allows moving between highlight and unhighlight state only if setHighlighted is not overwritten
  657. if (self.extVars.touchingIndexPath) {
  658. CGPoint touchPoint = [[touches anyObject] locationInView:self];
  659. NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
  660. // moving out of bounds
  661. if ([self.extVars.currentIndexPath isEqual:self.extVars.touchingIndexPath] &&
  662. ![indexPath isEqual:self.extVars.touchingIndexPath] &&
  663. [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES shouldCheckHighlight:YES]) {
  664. self.extVars.currentIndexPath = indexPath;
  665. // moving back into the original touching cell
  666. }else if (![self.extVars.currentIndexPath isEqual:self.extVars.touchingIndexPath] &&
  667. [indexPath isEqual:self.extVars.touchingIndexPath]) {
  668. [self highlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES];
  669. self.extVars.currentIndexPath = self.extVars.touchingIndexPath;
  670. }
  671. }
  672. }
  673. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  674. [super touchesEnded:touches withEvent:event];
  675. if (self.extVars.touchingIndexPath) {
  676. // first unhighlight the touch operation
  677. [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES];
  678. CGPoint touchPoint = [[touches anyObject] locationInView:self];
  679. NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
  680. if ([indexPath isEqual:self.extVars.touchingIndexPath]) {
  681. [self userSelectedItemAtIndexPath:indexPath];
  682. }
  683. else if (!self.allowsMultipleSelection) {
  684. NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
  685. if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:self.extVars.touchingIndexPath]) {
  686. [self cellTouchCancelled];
  687. }
  688. }
  689. // for pedantic reasons only - always set to nil on touchesBegan
  690. self.extVars.touchingIndexPath = nil;
  691. self.extVars.currentIndexPath = nil;
  692. }
  693. }
  694. - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  695. [super touchesCancelled:touches withEvent:event];
  696. // do not mark touchingIndexPath as nil because whoever cancelled this touch will need to signal a touch up event later
  697. if (self.extVars.touchingIndexPath) {
  698. // first unhighlight the touch operation
  699. [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES];
  700. }
  701. }
  702. - (void)cellTouchCancelled {
  703. // turn on ALL the *should be selected* cells (iOS6 UICollectionView does no state keeping or other fancy optimizations)
  704. // there should be no notifications as this is a silent "turn everything back on"
  705. for (NSIndexPath *tempDeselectedIndexPath in [_indexPathsForSelectedItems copy]) {
  706. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:tempDeselectedIndexPath];
  707. selectedCell.selected = YES;
  708. }
  709. }
  710. - (void)userSelectedItemAtIndexPath:(NSIndexPath *)indexPath {
  711. if (self.allowsMultipleSelection && [_indexPathsForSelectedItems containsObject:indexPath]) {
  712. [self deselectItemAtIndexPath:indexPath animated:YES notifyDelegate:YES];
  713. }
  714. else if (self.allowsSelection) {
  715. [self selectItemAtIndexPath:indexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES];
  716. }
  717. }
  718. // select item, notify delegate (internal)
  719. - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate {
  720. if (self.allowsMultipleSelection && [_indexPathsForSelectedItems containsObject:indexPath]) {
  721. BOOL shouldDeselect = YES;
  722. if (notifyDelegate && _collectionViewFlags.delegateShouldDeselectItemAtIndexPath) {
  723. shouldDeselect = [self.delegate collectionView:self shouldDeselectItemAtIndexPath:indexPath];
  724. }
  725. if (shouldDeselect) {
  726. [self deselectItemAtIndexPath:indexPath animated:animated notifyDelegate:notifyDelegate];
  727. }
  728. }
  729. else {
  730. // either single selection, or wasn't already selected in multiple selection mode
  731. BOOL shouldSelect = YES;
  732. if (notifyDelegate && _collectionViewFlags.delegateShouldSelectItemAtIndexPath) {
  733. shouldSelect = [self.delegate collectionView:self shouldSelectItemAtIndexPath:indexPath];
  734. }
  735. if (!self.allowsMultipleSelection) {
  736. // now unselect the previously selected cell for single selection
  737. NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
  738. if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:indexPath]) {
  739. [self deselectItemAtIndexPath:tempDeselectIndexPath animated:YES notifyDelegate:YES];
  740. }
  741. }
  742. if (shouldSelect) {
  743. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
  744. selectedCell.selected = YES;
  745. [_indexPathsForSelectedItems addObject:indexPath];
  746. [selectedCell performSelectionSegue];
  747. if (scrollPosition != PSTCollectionViewScrollPositionNone) {
  748. [self scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
  749. }
  750. if (notifyDelegate && _collectionViewFlags.delegateDidSelectItemAtIndexPath) {
  751. [self.delegate collectionView:self didSelectItemAtIndexPath:indexPath];
  752. }
  753. }
  754. }
  755. }
  756. - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition {
  757. [self selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition notifyDelegate:NO];
  758. }
  759. - (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated {
  760. [self deselectItemAtIndexPath:indexPath animated:animated notifyDelegate:NO];
  761. }
  762. - (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate {
  763. BOOL shouldDeselect = YES;
  764. // deselect only relevant during multi mode
  765. if (self.allowsMultipleSelection && notifyDelegate && _collectionViewFlags.delegateShouldDeselectItemAtIndexPath) {
  766. shouldDeselect = [self.delegate collectionView:self shouldDeselectItemAtIndexPath:indexPath];
  767. }
  768. if (shouldDeselect && [_indexPathsForSelectedItems containsObject:indexPath]) {
  769. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
  770. if (selectedCell) {
  771. if (selectedCell.selected) {
  772. selectedCell.selected = NO;
  773. }
  774. }
  775. [_indexPathsForSelectedItems removeObject:indexPath];
  776. if (notifyDelegate && _collectionViewFlags.delegateDidDeselectItemAtIndexPath) {
  777. [self.delegate collectionView:self didDeselectItemAtIndexPath:indexPath];
  778. }
  779. }
  780. }
  781. - (BOOL)highlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate {
  782. BOOL shouldHighlight = YES;
  783. if (notifyDelegate && _collectionViewFlags.delegateShouldHighlightItemAtIndexPath) {
  784. shouldHighlight = [self.delegate collectionView:self shouldHighlightItemAtIndexPath:indexPath];
  785. }
  786. if (shouldHighlight) {
  787. PSTCollectionViewCell *highlightedCell = [self cellForItemAtIndexPath:indexPath];
  788. highlightedCell.highlighted = YES;
  789. [_indexPathsForHighlightedItems addObject:indexPath];
  790. if (scrollPosition != PSTCollectionViewScrollPositionNone) {
  791. [self scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
  792. }
  793. if (notifyDelegate && _collectionViewFlags.delegateDidHighlightItemAtIndexPath) {
  794. [self.delegate collectionView:self didHighlightItemAtIndexPath:indexPath];
  795. }
  796. }
  797. return shouldHighlight;
  798. }
  799. - (BOOL)unhighlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate {
  800. return [self unhighlightItemAtIndexPath:indexPath animated:animated notifyDelegate:notifyDelegate shouldCheckHighlight:NO];
  801. }
  802. - (BOOL)unhighlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate shouldCheckHighlight:(BOOL)check {
  803. if ([_indexPathsForHighlightedItems containsObject:indexPath]) {
  804. PSTCollectionViewCell *highlightedCell = [self cellForItemAtIndexPath:indexPath];
  805. // iOS6 does not notify any delegate if the cell was never highlighted (setHighlighted overwritten) during touchMoved
  806. if (check && !highlightedCell.highlighted) {
  807. return NO;
  808. }
  809. // if multiple selection or not unhighlighting a selected item we don't perform any op
  810. if (highlightedCell.highlighted && [_indexPathsForSelectedItems containsObject:indexPath]) {
  811. highlightedCell.highlighted = YES;
  812. }else {
  813. highlightedCell.highlighted = NO;
  814. }
  815. [_indexPathsForHighlightedItems removeObject:indexPath];
  816. if (notifyDelegate && _collectionViewFlags.delegateDidUnhighlightItemAtIndexPath) {
  817. [self.delegate collectionView:self didUnhighlightItemAtIndexPath:indexPath];
  818. }
  819. return YES;
  820. }
  821. return NO;
  822. }
  823. ///////////////////////////////////////////////////////////////////////////////////////////
  824. #pragma mark - Update Grid
  825. - (void)insertSections:(NSIndexSet *)sections {
  826. [self updateSections:sections updateAction:PSTCollectionUpdateActionInsert];
  827. }
  828. - (void)deleteSections:(NSIndexSet *)sections {
  829. // First delete all items
  830. NSMutableArray *paths = [NSMutableArray new];
  831. [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
  832. for (int i = 0; i < [self numberOfItemsInSection:idx]; ++i) {
  833. [paths addObject:[NSIndexPath indexPathForItem:i inSection:idx]];
  834. }
  835. }];
  836. [self deleteItemsAtIndexPaths:paths];
  837. // Then delete the section.
  838. [self updateSections:sections updateAction:PSTCollectionUpdateActionDelete];
  839. }
  840. - (void)reloadSections:(NSIndexSet *)sections {
  841. [self updateSections:sections updateAction:PSTCollectionUpdateActionReload];
  842. }
  843. - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection {
  844. NSMutableArray *moveUpdateItems = [self arrayForUpdateAction:PSTCollectionUpdateActionMove];
  845. [moveUpdateItems addObject:
  846. [[PSTCollectionViewUpdateItem alloc] initWithInitialIndexPath:[NSIndexPath indexPathForItem:NSNotFound inSection:section]
  847. finalIndexPath:[NSIndexPath indexPathForItem:NSNotFound inSection:newSection]
  848. updateAction:PSTCollectionUpdateActionMove]];
  849. if (!_collectionViewFlags.updating) {
  850. [self setupCellAnimations];
  851. [self endItemAnimations];
  852. }
  853. }
  854. - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths {
  855. [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionInsert];
  856. }
  857. - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths {
  858. [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionDelete];
  859. }
  860. - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths {
  861. [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionReload];
  862. }
  863. - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath {
  864. NSMutableArray *moveUpdateItems = [self arrayForUpdateAction:PSTCollectionUpdateActionMove];
  865. [moveUpdateItems addObject:
  866. [[PSTCollectionViewUpdateItem alloc] initWithInitialIndexPath:indexPath
  867. finalIndexPath:newIndexPath
  868. updateAction:PSTCollectionUpdateActionMove]];
  869. if (!_collectionViewFlags.updating) {
  870. [self setupCellAnimations];
  871. [self endItemAnimations];
  872. }
  873. }
  874. - (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion {
  875. [self setupCellAnimations];
  876. if (updates) updates();
  877. if (completion) _updateCompletionHandler = completion;
  878. [self endItemAnimations];
  879. }
  880. ///////////////////////////////////////////////////////////////////////////////////////////
  881. #pragma mark - Properties
  882. - (void)setBackgroundView:(UIView *)backgroundView {
  883. if (backgroundView != _backgroundView) {
  884. [_backgroundView removeFromSuperview];
  885. _backgroundView = backgroundView;
  886. backgroundView.frame = (CGRect){.origin=self.contentOffset, .size=self.bounds.size};
  887. backgroundView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
  888. [self addSubview:backgroundView];
  889. [self sendSubviewToBack:backgroundView];
  890. }
  891. }
  892. - (void)setCollectionViewLayout:(PSTCollectionViewLayout *)layout animated:(BOOL)animated {
  893. if (layout == _layout) return;
  894. // not sure it was it original code, but here this prevents crash
  895. // in case we switch layout before previous one was initially loaded
  896. if (CGRectIsEmpty(self.bounds) || !_collectionViewFlags.doneFirstLayout) {
  897. _layout.collectionView = nil;
  898. _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
  899. layout.collectionView = self;
  900. _layout = layout;
  901. // originally the use method
  902. // _setNeedsVisibleCellsUpdate:withLayoutAttributes:
  903. // here with CellsUpdate set to YES and LayoutAttributes parameter set to NO
  904. // inside this method probably some flags are set and finally
  905. // setNeedsDisplay is called
  906. _collectionViewFlags.scheduledUpdateVisibleCells = YES;
  907. _collectionViewFlags.scheduledUpdateVisibleCellLayoutAttributes = NO;
  908. [self setNeedsDisplay];
  909. }
  910. else {
  911. layout.collectionView = self;
  912. _layout.collectionView = nil;
  913. _layout = layout;
  914. _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
  915. [_collectionViewData prepareToLoadData];
  916. NSArray *previouslySelectedIndexPaths = [self indexPathsForSelectedItems];
  917. NSMutableSet *selectedCellKeys = [NSMutableSet setWithCapacity:previouslySelectedIndexPaths.count];
  918. for (NSIndexPath *indexPath in previouslySelectedIndexPaths) {
  919. [selectedCellKeys addObject:[PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath]];
  920. }
  921. NSArray *previouslyVisibleItemsKeys = [_allVisibleViewsDict allKeys];
  922. NSSet *previouslyVisibleItemsKeysSet = [NSSet setWithArray:previouslyVisibleItemsKeys];
  923. NSMutableSet *previouslyVisibleItemsKeysSetMutable = [NSMutableSet setWithArray:previouslyVisibleItemsKeys];
  924. if ([selectedCellKeys intersectsSet:selectedCellKeys]) {
  925. [previouslyVisibleItemsKeysSetMutable intersectSet:previouslyVisibleItemsKeysSetMutable];
  926. }
  927. [self bringSubviewToFront:_allVisibleViewsDict[[previouslyVisibleItemsKeysSetMutable anyObject]]];
  928. CGPoint targetOffset = self.contentOffset;
  929. CGPoint centerPoint = CGPointMake(self.bounds.origin.x + self.bounds.size.width / 2.f,
  930. self.bounds.origin.y + self.bounds.size.height / 2.f);
  931. NSIndexPath *centerItemIndexPath = [self indexPathForItemAtPoint:centerPoint];
  932. if (!centerItemIndexPath) {
  933. NSArray *visibleItems = [self indexPathsForVisibleItems];
  934. if (visibleItems.count > 0) {
  935. centerItemIndexPath = visibleItems[visibleItems.count / 2];
  936. }
  937. }
  938. if (centerItemIndexPath) {
  939. PSTCo