PSTCollectionView.m 110 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318
  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. #import <dlfcn.h>
  13. @interface PSTCollectionViewLayout (Internal)
  14. @property (nonatomic, unsafe_unretained) PSTCollectionView *collectionView;
  15. @end
  16. @interface PSTCollectionViewData (Internal)
  17. - (void)prepareToLoadData;
  18. @end
  19. @interface PSTCollectionViewCell (Internal)
  20. - (void)performSelectionSegue;
  21. @end
  22. @interface PSTCollectionViewUpdateItem ()
  23. - (NSIndexPath *)indexPath;
  24. - (BOOL)isSectionOperation;
  25. @end
  26. @interface PSTCollectionViewLayoutAttributes () {
  27. char junk[128];
  28. }
  29. @property (nonatomic, copy) NSString *elementKind;
  30. @end
  31. CGFloat PSTSimulatorAnimationDragCoefficient(void);
  32. @class PSTCollectionViewExt;
  33. @interface PSTCollectionView () <UIScrollViewDelegate> {
  34. // ivar layout needs to EQUAL to UICollectionView.
  35. PSTCollectionViewLayout *_layout;
  36. __unsafe_unretained id<PSTCollectionViewDataSource> _dataSource;
  37. UIView *_backgroundView;
  38. NSMutableSet *_indexPathsForSelectedItems;
  39. NSMutableDictionary *_cellReuseQueues;
  40. NSMutableDictionary *_supplementaryViewReuseQueues;
  41. NSMutableDictionary *_decorationViewReuseQueues;
  42. NSMutableSet *_indexPathsForHighlightedItems;
  43. int _reloadingSuspendedCount;
  44. PSTCollectionReusableView *_firstResponderView;
  45. UIView *_newContentView;
  46. int _firstResponderViewType;
  47. NSString *_firstResponderViewKind;
  48. NSIndexPath *_firstResponderIndexPath;
  49. NSMutableDictionary *_allVisibleViewsDict;
  50. NSIndexPath *_pendingSelectionIndexPath;
  51. NSMutableSet *_pendingDeselectionIndexPaths;
  52. PSTCollectionViewData *_collectionViewData;
  53. id _update;
  54. CGRect _visibleBoundRects;
  55. CGRect _preRotationBounds;
  56. CGPoint _rotationBoundsOffset;
  57. int _rotationAnimationCount;
  58. int _updateCount;
  59. NSMutableArray *_insertItems;
  60. NSMutableArray *_deleteItems;
  61. NSMutableArray *_reloadItems;
  62. NSMutableArray *_moveItems;
  63. NSArray *_originalInsertItems;
  64. NSArray *_originalDeleteItems;
  65. UITouch *_currentTouch;
  66. void (^_updateCompletionHandler)(BOOL finished);
  67. NSMutableDictionary *_cellClassDict;
  68. NSMutableDictionary *_cellNibDict;
  69. NSMutableDictionary *_supplementaryViewClassDict;
  70. NSMutableDictionary *_supplementaryViewNibDict;
  71. NSMutableDictionary *_cellNibExternalObjectsTables;
  72. NSMutableDictionary *_supplementaryViewNibExternalObjectsTables;
  73. struct {
  74. unsigned int delegateShouldHighlightItemAtIndexPath : 1;
  75. unsigned int delegateDidHighlightItemAtIndexPath : 1;
  76. unsigned int delegateDidUnhighlightItemAtIndexPath : 1;
  77. unsigned int delegateShouldSelectItemAtIndexPath : 1;
  78. unsigned int delegateShouldDeselectItemAtIndexPath : 1;
  79. unsigned int delegateDidSelectItemAtIndexPath : 1;
  80. unsigned int delegateDidDeselectItemAtIndexPath : 1;
  81. unsigned int delegateSupportsMenus : 1;
  82. unsigned int delegateDidEndDisplayingCell : 1;
  83. unsigned int delegateDidEndDisplayingSupplementaryView : 1;
  84. unsigned int dataSourceNumberOfSections : 1;
  85. unsigned int dataSourceViewForSupplementaryElement : 1;
  86. unsigned int reloadSkippedDuringSuspension : 1;
  87. unsigned int scheduledUpdateVisibleCells : 1;
  88. unsigned int scheduledUpdateVisibleCellLayoutAttributes : 1;
  89. unsigned int allowsSelection : 1;
  90. unsigned int allowsMultipleSelection : 1;
  91. unsigned int updating : 1;
  92. unsigned int fadeCellsForBoundsChange : 1;
  93. unsigned int updatingLayout : 1;
  94. unsigned int needsReload : 1;
  95. unsigned int reloading : 1;
  96. unsigned int skipLayoutDuringSnapshotting : 1;
  97. unsigned int layoutInvalidatedSinceLastCellUpdate : 1;
  98. unsigned int doneFirstLayout : 1;
  99. }_collectionViewFlags;
  100. CGPoint _lastLayoutOffset;
  101. char filler[800]; // [HACK] Our class needs to be larger than Apple's class for the superclass change to work.
  102. }
  103. @property (nonatomic, strong) PSTCollectionViewData *collectionViewData;
  104. @property (nonatomic, strong, readonly) PSTCollectionViewExt *extVars;
  105. @property (nonatomic, readonly) id currentUpdate;
  106. @property (nonatomic, readonly) NSDictionary *visibleViewsDict;
  107. @property (nonatomic, assign) CGRect visibleBoundRects;
  108. @end
  109. // Used by PSTCollectionView for external variables.
  110. // (We need to keep the total class size equal to the UICollectionView variant)
  111. @interface PSTCollectionViewExt : NSObject
  112. @property (nonatomic, unsafe_unretained) id<PSTCollectionViewDelegate> collectionViewDelegate;
  113. @property (nonatomic, strong) PSTCollectionViewLayout *nibLayout;
  114. @property (nonatomic, strong) NSDictionary *nibCellsExternalObjects;
  115. @property (nonatomic, strong) NSDictionary *supplementaryViewsExternalObjects;
  116. @property (nonatomic, strong) NSIndexPath *touchingIndexPath;
  117. @property (nonatomic, strong) NSIndexPath *currentIndexPath;
  118. @end
  119. @implementation PSTCollectionViewExt
  120. @end
  121. const char kPSTColletionViewExt;
  122. @implementation PSTCollectionView
  123. @synthesize collectionViewLayout = _layout;
  124. @synthesize currentUpdate = _update;
  125. @synthesize visibleViewsDict = _allVisibleViewsDict;
  126. ///////////////////////////////////////////////////////////////////////////////////////////
  127. #pragma mark - NSObject
  128. static void PSTCollectionViewCommonSetup(PSTCollectionView *_self) {
  129. _self.allowsSelection = YES;
  130. _self->_indexPathsForSelectedItems = [NSMutableSet new];
  131. _self->_indexPathsForHighlightedItems = [NSMutableSet new];
  132. _self->_cellReuseQueues = [NSMutableDictionary new];
  133. _self->_supplementaryViewReuseQueues = [NSMutableDictionary new];
  134. _self->_decorationViewReuseQueues = [NSMutableDictionary new];
  135. _self->_allVisibleViewsDict = [NSMutableDictionary new];
  136. _self->_cellClassDict = [NSMutableDictionary new];
  137. _self->_cellNibDict = [NSMutableDictionary new];
  138. _self->_supplementaryViewClassDict = [NSMutableDictionary new];
  139. _self->_supplementaryViewNibDict = [NSMutableDictionary new];
  140. // add class that saves additional ivars
  141. objc_setAssociatedObject(_self, &kPSTColletionViewExt, [PSTCollectionViewExt new], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  142. }
  143. - (id)initWithFrame:(CGRect)frame {
  144. return [self initWithFrame:frame collectionViewLayout:nil];
  145. }
  146. - (id)initWithFrame:(CGRect)frame collectionViewLayout:(PSTCollectionViewLayout *)layout {
  147. if ((self = [super initWithFrame:frame])) {
  148. // Set self as the UIScrollView's delegate
  149. [super setDelegate:self];
  150. PSTCollectionViewCommonSetup(self);
  151. self.collectionViewLayout = layout;
  152. _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
  153. }
  154. return self;
  155. }
  156. - (id)initWithCoder:(NSCoder *)inCoder {
  157. if ((self = [super initWithCoder:inCoder])) {
  158. // Set self as the UIScrollView's delegate
  159. [super setDelegate:self];
  160. PSTCollectionViewCommonSetup(self);
  161. self.extVars.nibLayout = [inCoder decodeObjectForKey:@"UICollectionLayout"];
  162. NSDictionary *cellExternalObjects = [inCoder decodeObjectForKey:@"UICollectionViewCellPrototypeNibExternalObjects"];
  163. NSDictionary *cellNibs = [inCoder decodeObjectForKey:@"UICollectionViewCellNibDict"];
  164. for (NSString *identifier in cellNibs.allKeys) {
  165. _cellNibDict[identifier] = cellNibs[identifier];
  166. }
  167. self.extVars.nibCellsExternalObjects = cellExternalObjects;
  168. NSDictionary *supplementaryViewExternalObjects = [inCoder decodeObjectForKey:@"UICollectionViewSupplementaryViewPrototypeNibExternalObjects"];
  169. NSDictionary *supplementaryViewNibs = [inCoder decodeObjectForKey:@"UICollectionViewSupplementaryViewNibDict"];
  170. for (NSString *identifier in supplementaryViewNibs.allKeys) {
  171. _supplementaryViewNibDict[identifier] = supplementaryViewNibs[identifier];
  172. }
  173. self.extVars.supplementaryViewsExternalObjects = supplementaryViewExternalObjects;
  174. }
  175. return self;
  176. }
  177. - (void)awakeFromNib {
  178. [super awakeFromNib];
  179. PSTCollectionViewLayout *nibLayout = self.extVars.nibLayout;
  180. if (nibLayout) {
  181. self.collectionViewLayout = nibLayout;
  182. self.extVars.nibLayout = nil;
  183. }
  184. }
  185. - (NSString *)description {
  186. return [NSString stringWithFormat:@"%@ collection view layout: %@", [super description], self.collectionViewLayout];
  187. }
  188. ///////////////////////////////////////////////////////////////////////////////////////////
  189. #pragma mark - UIView
  190. - (void)layoutSubviews {
  191. [super layoutSubviews];
  192. // Adding alpha animation to make the relayouting smooth
  193. if (_collectionViewFlags.fadeCellsForBoundsChange) {
  194. CATransition *transition = [CATransition animation];
  195. transition.duration = 0.25f * PSTSimulatorAnimationDragCoefficient();
  196. transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
  197. transition.type = kCATransitionFade;
  198. [self.layer addAnimation:transition forKey:@"rotationAnimation"];
  199. }
  200. [_collectionViewData validateLayoutInRect:self.bounds];
  201. // update cells
  202. if (_collectionViewFlags.fadeCellsForBoundsChange) {
  203. [CATransaction begin];
  204. [CATransaction setDisableActions:YES];
  205. }
  206. if (!_collectionViewFlags.updatingLayout)
  207. [self updateVisibleCellsNow:YES];
  208. if (_collectionViewFlags.fadeCellsForBoundsChange) {
  209. [CATransaction commit];
  210. }
  211. // do we need to update contentSize?
  212. CGSize contentSize = [_collectionViewData collectionViewContentRect].size;
  213. if (!CGSizeEqualToSize(self.contentSize, contentSize)) {
  214. self.contentSize = contentSize;
  215. // if contentSize is different, we need to re-evaluate layout, bounds (contentOffset) might changed
  216. [_collectionViewData validateLayoutInRect:self.bounds];
  217. [self updateVisibleCellsNow:YES];
  218. }
  219. if (_backgroundView) {
  220. _backgroundView.frame = (CGRect){.origin=self.contentOffset, .size=self.bounds.size};
  221. }
  222. _collectionViewFlags.fadeCellsForBoundsChange = NO;
  223. _collectionViewFlags.doneFirstLayout = YES;
  224. }
  225. - (void)setFrame:(CGRect)frame {
  226. if (!CGRectEqualToRect(frame, self.frame)) {
  227. [super setFrame:frame];
  228. if ([self.collectionViewLayout shouldInvalidateLayoutForBoundsChange:self.bounds]) {
  229. [self invalidateLayout];
  230. _collectionViewFlags.fadeCellsForBoundsChange = YES;
  231. }
  232. }
  233. }
  234. - (void)setBounds:(CGRect)bounds {
  235. if (!CGRectEqualToRect(bounds, self.bounds)) {
  236. [super setBounds:bounds];
  237. if ([self.collectionViewLayout shouldInvalidateLayoutForBoundsChange:bounds]) {
  238. [self invalidateLayout];
  239. _collectionViewFlags.fadeCellsForBoundsChange = YES;
  240. }
  241. }
  242. }
  243. ///////////////////////////////////////////////////////////////////////////////////////////
  244. #pragma mark - UIScrollViewDelegate
  245. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  246. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  247. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
  248. [delegate scrollViewDidScroll:scrollView];
  249. }
  250. }
  251. - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  252. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  253. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidZoom:)]) {
  254. [delegate scrollViewDidZoom:scrollView];
  255. }
  256. }
  257. - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
  258. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  259. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
  260. [delegate scrollViewWillBeginDragging:scrollView];
  261. }
  262. }
  263. - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
  264. // Let collectionViewLayout decide where to stop.
  265. *targetContentOffset = [[self collectionViewLayout] targetContentOffsetForProposedContentOffset:*targetContentOffset withScrollingVelocity:velocity];
  266. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  267. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
  268. //if collectionViewDelegate implements this method, it may modify targetContentOffset as well
  269. [delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
  270. }
  271. }
  272. - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
  273. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  274. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) {
  275. [delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
  276. }
  277. // if we are in the middle of a cell touch event, perform the "touchEnded" simulation
  278. if (self.extVars.touchingIndexPath) {
  279. [self cellTouchCancelled];
  280. }
  281. }
  282. - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
  283. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  284. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)]) {
  285. [delegate scrollViewWillBeginDecelerating:scrollView];
  286. }
  287. }
  288. - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
  289. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  290. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) {
  291. [delegate scrollViewDidEndDecelerating:scrollView];
  292. }
  293. }
  294. - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
  295. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  296. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) {
  297. [delegate scrollViewDidEndScrollingAnimation:scrollView];
  298. }
  299. }
  300. - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  301. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  302. if ((id)delegate != self && [delegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
  303. return [delegate viewForZoomingInScrollView:scrollView];
  304. }
  305. return nil;
  306. }
  307. - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
  308. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  309. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
  310. [delegate scrollViewWillBeginZooming:scrollView withView:view];
  311. }
  312. }
  313. - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
  314. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  315. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)]) {
  316. [delegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
  317. }
  318. }
  319. - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
  320. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  321. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) {
  322. return [delegate scrollViewShouldScrollToTop:scrollView];
  323. }
  324. return YES;
  325. }
  326. - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
  327. id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
  328. if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidScrollToTop:)]) {
  329. [delegate scrollViewDidScrollToTop:scrollView];
  330. }
  331. }
  332. ///////////////////////////////////////////////////////////////////////////////////////////
  333. #pragma mark - Public
  334. - (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier {
  335. NSParameterAssert(cellClass);
  336. NSParameterAssert(identifier);
  337. _cellClassDict[identifier] = cellClass;
  338. }
  339. - (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier {
  340. NSParameterAssert(viewClass);
  341. NSParameterAssert(elementKind);
  342. NSParameterAssert(identifier);
  343. NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", elementKind, identifier];
  344. _supplementaryViewClassDict[kindAndIdentifier] = viewClass;
  345. }
  346. - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier {
  347. NSArray *topLevelObjects = [nib instantiateWithOwner:nil options:nil];
  348. #pragma unused(topLevelObjects)
  349. NSAssert(topLevelObjects.count == 1 && [topLevelObjects[0] isKindOfClass:PSTCollectionViewCell.class], @"must contain exactly 1 top level object which is a PSTCollectionViewCell");
  350. _cellNibDict[identifier] = nib;
  351. }
  352. - (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier {
  353. NSArray *topLevelObjects = [nib instantiateWithOwner:nil options:nil];
  354. #pragma unused(topLevelObjects)
  355. NSAssert(topLevelObjects.count == 1 && [topLevelObjects[0] isKindOfClass:PSTCollectionReusableView.class], @"must contain exactly 1 top level object which is a PSTCollectionReusableView");
  356. NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", kind, identifier];
  357. _supplementaryViewNibDict[kindAndIdentifier] = nib;
  358. }
  359. - (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
  360. // de-queue cell (if available)
  361. NSMutableArray *reusableCells = _cellReuseQueues[identifier];
  362. PSTCollectionViewCell *cell = [reusableCells lastObject];
  363. PSTCollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
  364. if (cell) {
  365. [reusableCells removeObjectAtIndex:reusableCells.count - 1];
  366. }else {
  367. if (_cellNibDict[identifier]) {
  368. // Cell was registered via registerNib:forCellWithReuseIdentifier:
  369. UINib *cellNib = _cellNibDict[identifier];
  370. NSDictionary *externalObjects = self.extVars.nibCellsExternalObjects[identifier];
  371. if (externalObjects) {
  372. cell = [cellNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
  373. }else {
  374. cell = [cellNib instantiateWithOwner:self options:nil][0];
  375. }
  376. }else {
  377. Class cellClass = _cellClassDict[identifier];
  378. // compatibility layer
  379. Class collectionViewCellClass = NSClassFromString(@"UICollectionViewCell");
  380. if (collectionViewCellClass && [cellClass isEqual:collectionViewCellClass]) {
  381. cellClass = PSTCollectionViewCell.class;
  382. }
  383. if (cellClass == nil) {
  384. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for identifier %@", identifier] userInfo:nil];
  385. }
  386. if (attributes) {
  387. cell = [[cellClass alloc] initWithFrame:attributes.frame];
  388. }else {
  389. cell = [cellClass new];
  390. }
  391. }
  392. cell.collectionView = self;
  393. cell.reuseIdentifier = identifier;
  394. }
  395. [cell applyLayoutAttributes:attributes];
  396. return cell;
  397. }
  398. - (id)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
  399. NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", elementKind, identifier];
  400. NSMutableArray *reusableViews = _supplementaryViewReuseQueues[kindAndIdentifier];
  401. PSTCollectionReusableView *view = [reusableViews lastObject];
  402. if (view) {
  403. [reusableViews removeObjectAtIndex:reusableViews.count - 1];
  404. }else {
  405. if (_supplementaryViewNibDict[kindAndIdentifier]) {
  406. // supplementary view was registered via registerNib:forCellWithReuseIdentifier:
  407. UINib *supplementaryViewNib = _supplementaryViewNibDict[kindAndIdentifier];
  408. NSDictionary *externalObjects = self.extVars.supplementaryViewsExternalObjects[kindAndIdentifier];
  409. if (externalObjects) {
  410. view = [supplementaryViewNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
  411. }else {
  412. view = [supplementaryViewNib instantiateWithOwner:self options:nil][0];
  413. }
  414. }else {
  415. Class viewClass = _supplementaryViewClassDict[kindAndIdentifier];
  416. Class reusableViewClass = NSClassFromString(@"UICollectionReusableView");
  417. if (reusableViewClass && [viewClass isEqual:reusableViewClass]) {
  418. viewClass = PSTCollectionReusableView.class;
  419. }
  420. if (viewClass == nil) {
  421. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for kind/identifier %@", kindAndIdentifier] userInfo:nil];
  422. }
  423. if (self.collectionViewLayout) {
  424. PSTCollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath];
  425. if (attributes) {
  426. view = [[viewClass alloc] initWithFrame:attributes.frame];
  427. }
  428. }else {
  429. view = [viewClass new];
  430. }
  431. }
  432. view.collectionView = self;
  433. view.reuseIdentifier = identifier;
  434. }
  435. return view;
  436. }
  437. - (id)dequeueReusableOrCreateDecorationViewOfKind:(NSString *)elementKind forIndexPath:(NSIndexPath *)indexPath {
  438. NSMutableArray *reusableViews = _decorationViewReuseQueues[elementKind];
  439. PSTCollectionReusableView *view = [reusableViews lastObject];
  440. PSTCollectionViewLayout *collectionViewLayout = self.collectionViewLayout;
  441. PSTCollectionViewLayoutAttributes *attributes = [collectionViewLayout layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:indexPath];
  442. if (view) {
  443. [reusableViews removeObjectAtIndex:reusableViews.count - 1];
  444. }else {
  445. NSDictionary *decorationViewNibDict = collectionViewLayout.decorationViewNibDict;
  446. NSDictionary *decorationViewExternalObjects = collectionViewLayout.decorationViewExternalObjectsTables;
  447. if (decorationViewNibDict[elementKind]) {
  448. // supplementary view was registered via registerNib:forCellWithReuseIdentifier:
  449. UINib *supplementaryViewNib = decorationViewNibDict[elementKind];
  450. NSDictionary *externalObjects = decorationViewExternalObjects[elementKind];
  451. if (externalObjects) {
  452. view = [supplementaryViewNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
  453. }else {
  454. view = [supplementaryViewNib instantiateWithOwner:self options:nil][0];
  455. }
  456. }else {
  457. NSDictionary *decorationViewClassDict = collectionViewLayout.decorationViewClassDict;
  458. Class viewClass = decorationViewClassDict[elementKind];
  459. Class reusableViewClass = NSClassFromString(@"UICollectionReusableView");
  460. if (reusableViewClass && [viewClass isEqual:reusableViewClass]) {
  461. viewClass = PSTCollectionReusableView.class;
  462. }
  463. if (viewClass == nil) {
  464. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for identifier %@", elementKind] userInfo:nil];
  465. }
  466. if (attributes) {
  467. view = [[viewClass alloc] initWithFrame:attributes.frame];
  468. }else {
  469. view = [viewClass new];
  470. }
  471. }
  472. view.collectionView = self;
  473. view.reuseIdentifier = elementKind;
  474. }
  475. [view applyLayoutAttributes:attributes];
  476. return view;
  477. }
  478. - (NSArray *)allCells {
  479. return [[_allVisibleViewsDict allValues] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  480. return [evaluatedObject isKindOfClass:PSTCollectionViewCell.class];
  481. }]];
  482. }
  483. - (NSArray *)visibleCells {
  484. return [[_allVisibleViewsDict allValues] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  485. return [evaluatedObject isKindOfClass:PSTCollectionViewCell.class] && CGRectIntersectsRect(self.bounds, [evaluatedObject frame]);
  486. }]];
  487. }
  488. - (void)reloadData {
  489. if (_reloadingSuspendedCount != 0) return;
  490. [self invalidateLayout];
  491. [_allVisibleViewsDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
  492. if ([obj isKindOfClass:UIView.class]) {
  493. [obj removeFromSuperview];
  494. }
  495. }];
  496. [_allVisibleViewsDict removeAllObjects];
  497. for (NSIndexPath *indexPath in _indexPathsForSelectedItems) {
  498. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
  499. selectedCell.selected = NO;
  500. selectedCell.highlighted = NO;
  501. }
  502. [_indexPathsForSelectedItems removeAllObjects];
  503. [_indexPathsForHighlightedItems removeAllObjects];
  504. [self setNeedsLayout];
  505. }
  506. ///////////////////////////////////////////////////////////////////////////////////////////
  507. #pragma mark - Query Grid
  508. - (NSInteger)numberOfSections {
  509. return [_collectionViewData numberOfSections];
  510. }
  511. - (NSInteger)numberOfItemsInSection:(NSInteger)section {
  512. return [_collectionViewData numberOfItemsInSection:section];
  513. }
  514. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
  515. return [[self collectionViewLayout] layoutAttributesForItemAtIndexPath:indexPath];
  516. }
  517. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
  518. return [[self collectionViewLayout] layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
  519. }
  520. - (NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point {
  521. __block NSIndexPath *indexPath = nil;
  522. [_allVisibleViewsDict enumerateKeysAndObjectsWithOptions:kNilOptions usingBlock:^(id key, id obj, BOOL *stop) {
  523. PSTCollectionViewItemKey *itemKey = (PSTCollectionViewItemKey *)key;
  524. if (itemKey.type == PSTCollectionViewItemTypeCell) {
  525. PSTCollectionViewCell *cell = (PSTCollectionViewCell *)obj;
  526. if (CGRectContainsPoint(cell.frame, point) && cell.userInteractionEnabled) {
  527. indexPath = itemKey.indexPath;
  528. *stop = YES;
  529. }
  530. }
  531. }];
  532. return indexPath;
  533. }
  534. - (NSIndexPath *)indexPathForCell:(PSTCollectionViewCell *)cell {
  535. __block NSIndexPath *indexPath = nil;
  536. [_allVisibleViewsDict enumerateKeysAndObjectsWithOptions:kNilOptions usingBlock:^(id key, id obj, BOOL *stop) {
  537. PSTCollectionViewItemKey *itemKey = (PSTCollectionViewItemKey *)key;
  538. if (itemKey.type == PSTCollectionViewItemTypeCell) {
  539. PSTCollectionViewCell *currentCell = (PSTCollectionViewCell *)obj;
  540. if (currentCell == cell) {
  541. indexPath = itemKey.indexPath;
  542. *stop = YES;
  543. }
  544. }
  545. }];
  546. return indexPath;
  547. }
  548. - (PSTCollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath {
  549. // NSInteger index = [_collectionViewData globalIndexForItemAtIndexPath:indexPath];
  550. // TODO Apple uses some kind of globalIndex for this.
  551. __block PSTCollectionViewCell *cell = nil;
  552. [_allVisibleViewsDict enumerateKeysAndObjectsWithOptions:0 usingBlock:^(id key, id obj, BOOL *stop) {
  553. PSTCollectionViewItemKey *itemKey = (PSTCollectionViewItemKey *)key;
  554. if (itemKey.type == PSTCollectionViewItemTypeCell) {
  555. if ([itemKey.indexPath isEqual:indexPath]) {
  556. cell = obj;
  557. *stop = YES;
  558. }
  559. }
  560. }];
  561. return cell;
  562. }
  563. - (NSArray *)indexPathsForVisibleItems {
  564. NSArray *visibleCells = self.visibleCells;
  565. NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:visibleCells.count];
  566. [visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  567. PSTCollectionViewCell *cell = (PSTCollectionViewCell *)obj;
  568. [indexPaths addObject:cell.layoutAttributes.indexPath];
  569. }];
  570. return indexPaths;
  571. }
  572. // returns nil or an array of selected index paths
  573. - (NSArray *)indexPathsForSelectedItems {
  574. return [_indexPathsForSelectedItems allObjects];
  575. }
  576. // Interacting with the collection view.
  577. - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(PSTCollectionViewScrollPosition)scrollPosition animated:(BOOL)animated {
  578. // Ensure grid is laid out; else we can't scroll.
  579. [self layoutSubviews];
  580. PSTCollectionViewLayoutAttributes *layoutAttributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
  581. if (layoutAttributes) {
  582. CGRect targetRect = [self makeRect:layoutAttributes.frame toScrollPosition:scrollPosition];
  583. [self scrollRectToVisible:targetRect animated:animated];
  584. }
  585. }
  586. - (CGRect)makeRect:(CGRect)targetRect toScrollPosition:(PSTCollectionViewScrollPosition)scrollPosition {
  587. // split parameters
  588. NSUInteger verticalPosition = scrollPosition&0x07; // 0000 0111
  589. NSUInteger horizontalPosition = scrollPosition&0x38; // 0011 1000
  590. if (verticalPosition != PSTCollectionViewScrollPositionNone
  591. && verticalPosition != PSTCollectionViewScrollPositionTop
  592. && verticalPosition != PSTCollectionViewScrollPositionCenteredVertically
  593. && verticalPosition != PSTCollectionViewScrollPositionBottom) {
  594. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"PSTCollectionViewScrollPosition: attempt to use a scroll position with multiple vertical positioning styles" userInfo:nil];
  595. }
  596. if (horizontalPosition != PSTCollectionViewScrollPositionNone
  597. && horizontalPosition != PSTCollectionViewScrollPositionLeft
  598. && horizontalPosition != PSTCollectionViewScrollPositionCenteredHorizontally
  599. && horizontalPosition != PSTCollectionViewScrollPositionRight) {
  600. @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"PSTCollectionViewScrollPosition: attempt to use a scroll position with multiple horizontal positioning styles" userInfo:nil];
  601. }
  602. CGRect frame = self.layer.bounds;
  603. CGFloat calculateX;
  604. CGFloat calculateY;
  605. switch (verticalPosition) {
  606. case PSTCollectionViewScrollPositionCenteredVertically:
  607. calculateY = fmax(targetRect.origin.y - ((frame.size.height / 2) - (targetRect.size.height / 2)), -self.contentInset.top);
  608. targetRect = CGRectMake(targetRect.origin.x, calculateY, targetRect.size.width, frame.size.height);
  609. break;
  610. case PSTCollectionViewScrollPositionTop:
  611. targetRect = CGRectMake(targetRect.origin.x, targetRect.origin.y, targetRect.size.width, frame.size.height);
  612. break;
  613. case PSTCollectionViewScrollPositionBottom:
  614. calculateY = fmax(targetRect.origin.y - (frame.size.height - targetRect.size.height), -self.contentInset.top);
  615. targetRect = CGRectMake(targetRect.origin.x, calculateY, targetRect.size.width, frame.size.height);
  616. break;
  617. }
  618. switch (horizontalPosition) {
  619. case PSTCollectionViewScrollPositionCenteredHorizontally:
  620. calculateX = targetRect.origin.x - ((frame.size.width / 2) - (targetRect.size.width / 2));
  621. targetRect = CGRectMake(calculateX, targetRect.origin.y, frame.size.width, targetRect.size.height);
  622. break;
  623. case PSTCollectionViewScrollPositionLeft:
  624. targetRect = CGRectMake(targetRect.origin.x, targetRect.origin.y, frame.size.width, targetRect.size.height);
  625. break;
  626. case PSTCollectionViewScrollPositionRight:
  627. calculateX = targetRect.origin.x - (frame.size.width - targetRect.size.width);
  628. targetRect = CGRectMake(calculateX, targetRect.origin.y, frame.size.width, targetRect.size.height);
  629. break;
  630. }
  631. return targetRect;
  632. }
  633. ///////////////////////////////////////////////////////////////////////////////////////////
  634. #pragma mark - Touch Handling
  635. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  636. [super touchesBegan:touches withEvent:event];
  637. // reset touching state vars
  638. self.extVars.touchingIndexPath = nil;
  639. self.extVars.currentIndexPath = nil;
  640. CGPoint touchPoint = [[touches anyObject] locationInView:self];
  641. NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
  642. if (indexPath && self.allowsSelection) {
  643. if (![self highlightItemAtIndexPath:indexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES])
  644. return;
  645. self.extVars.touchingIndexPath = indexPath;
  646. self.extVars.currentIndexPath = indexPath;
  647. if (!self.allowsMultipleSelection) {
  648. // temporally unhighlight background on touchesBegan (keeps selected by _indexPathsForSelectedItems)
  649. // single-select only mode only though
  650. NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
  651. if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:self.extVars.touchingIndexPath]) {
  652. // iOS6 UICollectionView deselects cell without notification
  653. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:tempDeselectIndexPath];
  654. selectedCell.selected = NO;
  655. }
  656. }
  657. }
  658. }
  659. - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  660. [super touchesMoved:touches withEvent:event];
  661. // allows moving between highlight and unhighlight state only if setHighlighted is not overwritten
  662. if (self.extVars.touchingIndexPath) {
  663. CGPoint touchPoint = [[touches anyObject] locationInView:self];
  664. NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
  665. // moving out of bounds
  666. if ([self.extVars.currentIndexPath isEqual:self.extVars.touchingIndexPath] &&
  667. ![indexPath isEqual:self.extVars.touchingIndexPath] &&
  668. [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES shouldCheckHighlight:YES]) {
  669. self.extVars.currentIndexPath = indexPath;
  670. // moving back into the original touching cell
  671. }else if (![self.extVars.currentIndexPath isEqual:self.extVars.touchingIndexPath] &&
  672. [indexPath isEqual:self.extVars.touchingIndexPath]) {
  673. [self highlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES];
  674. self.extVars.currentIndexPath = self.extVars.touchingIndexPath;
  675. }
  676. }
  677. }
  678. - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  679. [super touchesEnded:touches withEvent:event];
  680. if (self.extVars.touchingIndexPath) {
  681. // first unhighlight the touch operation
  682. [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES];
  683. CGPoint touchPoint = [[touches anyObject] locationInView:self];
  684. NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
  685. if ([indexPath isEqual:self.extVars.touchingIndexPath]) {
  686. [self userSelectedItemAtIndexPath:indexPath];
  687. }
  688. else if (!self.allowsMultipleSelection) {
  689. NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
  690. if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:self.extVars.touchingIndexPath]) {
  691. [self cellTouchCancelled];
  692. }
  693. }
  694. // for pedantic reasons only - always set to nil on touchesBegan
  695. self.extVars.touchingIndexPath = nil;
  696. self.extVars.currentIndexPath = nil;
  697. }
  698. }
  699. - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  700. [super touchesCancelled:touches withEvent:event];
  701. // do not mark touchingIndexPath as nil because whoever cancelled this touch will need to signal a touch up event later
  702. if (self.extVars.touchingIndexPath) {
  703. // first unhighlight the touch operation
  704. [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES];
  705. }
  706. }
  707. - (void)cellTouchCancelled {
  708. // turn on ALL the *should be selected* cells (iOS6 UICollectionView does no state keeping or other fancy optimizations)
  709. // there should be no notifications as this is a silent "turn everything back on"
  710. for (NSIndexPath *tempDeselectedIndexPath in [_indexPathsForSelectedItems copy]) {
  711. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:tempDeselectedIndexPath];
  712. selectedCell.selected = YES;
  713. }
  714. }
  715. - (void)userSelectedItemAtIndexPath:(NSIndexPath *)indexPath {
  716. if (self.allowsMultipleSelection && [_indexPathsForSelectedItems containsObject:indexPath]) {
  717. [self deselectItemAtIndexPath:indexPath animated:YES notifyDelegate:YES];
  718. }
  719. else if (self.allowsSelection) {
  720. [self selectItemAtIndexPath:indexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES];
  721. }
  722. }
  723. // select item, notify delegate (internal)
  724. - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate {
  725. if (self.allowsMultipleSelection && [_indexPathsForSelectedItems containsObject:indexPath]) {
  726. BOOL shouldDeselect = YES;
  727. if (notifyDelegate && _collectionViewFlags.delegateShouldDeselectItemAtIndexPath) {
  728. shouldDeselect = [self.delegate collectionView:self shouldDeselectItemAtIndexPath:indexPath];
  729. }
  730. if (shouldDeselect) {
  731. [self deselectItemAtIndexPath:indexPath animated:animated notifyDelegate:notifyDelegate];
  732. }
  733. }
  734. else {
  735. // either single selection, or wasn't already selected in multiple selection mode
  736. BOOL shouldSelect = YES;
  737. if (notifyDelegate && _collectionViewFlags.delegateShouldSelectItemAtIndexPath) {
  738. shouldSelect = [self.delegate collectionView:self shouldSelectItemAtIndexPath:indexPath];
  739. }
  740. if (!self.allowsMultipleSelection) {
  741. // now unselect the previously selected cell for single selection
  742. NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
  743. if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:indexPath]) {
  744. [self deselectItemAtIndexPath:tempDeselectIndexPath animated:YES notifyDelegate:YES];
  745. }
  746. }
  747. if (shouldSelect) {
  748. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
  749. selectedCell.selected = YES;
  750. [_indexPathsForSelectedItems addObject:indexPath];
  751. [selectedCell performSelectionSegue];
  752. if (scrollPosition != PSTCollectionViewScrollPositionNone) {
  753. [self scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
  754. }
  755. if (notifyDelegate && _collectionViewFlags.delegateDidSelectItemAtIndexPath) {
  756. [self.delegate collectionView:self didSelectItemAtIndexPath:indexPath];
  757. }
  758. }
  759. }
  760. }
  761. - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition {
  762. [self selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition notifyDelegate:NO];
  763. }
  764. - (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated {
  765. [self deselectItemAtIndexPath:indexPath animated:animated notifyDelegate:NO];
  766. }
  767. - (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate {
  768. BOOL shouldDeselect = YES;
  769. // deselect only relevant during multi mode
  770. if (self.allowsMultipleSelection && notifyDelegate && _collectionViewFlags.delegateShouldDeselectItemAtIndexPath) {
  771. shouldDeselect = [self.delegate collectionView:self shouldDeselectItemAtIndexPath:indexPath];
  772. }
  773. if (shouldDeselect && [_indexPathsForSelectedItems containsObject:indexPath]) {
  774. PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
  775. if (selectedCell) {
  776. if (selectedCell.selected) {
  777. selectedCell.selected = NO;
  778. }
  779. }
  780. [_indexPathsForSelectedItems removeObject:indexPath];
  781. if (notifyDelegate && _collectionViewFlags.delegateDidDeselectItemAtIndexPath) {
  782. [self.delegate collectionView:self didDeselectItemAtIndexPath:indexPath];
  783. }
  784. }
  785. }
  786. - (BOOL)highlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate {
  787. BOOL shouldHighlight = YES;
  788. if (notifyDelegate && _collectionViewFlags.delegateShouldHighlightItemAtIndexPath) {
  789. shouldHighlight = [self.delegate collectionView:self shouldHighlightItemAtIndexPath:indexPath];
  790. }
  791. if (shouldHighlight) {
  792. PSTCollectionViewCell *highlightedCell = [self cellForItemAtIndexPath:indexPath];
  793. highlightedCell.highlighted = YES;
  794. [_indexPathsForHighlightedItems addObject:indexPath];
  795. if (scrollPosition != PSTCollectionViewScrollPositionNone) {
  796. [self scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
  797. }
  798. if (notifyDelegate && _collectionViewFlags.delegateDidHighlightItemAtIndexPath) {
  799. [self.delegate collectionView:self didHighlightItemAtIndexPath:indexPath];
  800. }
  801. }
  802. return shouldHighlight;
  803. }
  804. - (BOOL)unhighlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate {
  805. return [self unhighlightItemAtIndexPath:indexPath animated:animated notifyDelegate:notifyDelegate shouldCheckHighlight:NO];
  806. }
  807. - (BOOL)unhighlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate shouldCheckHighlight:(BOOL)check {
  808. if ([_indexPathsForHighlightedItems containsObject:indexPath]) {
  809. PSTCollectionViewCell *highlightedCell = [self cellForItemAtIndexPath:indexPath];
  810. // iOS6 does not notify any delegate if the cell was never highlighted (setHighlighted overwritten) during touchMoved
  811. if (check && !highlightedCell.highlighted) {
  812. return NO;
  813. }
  814. // if multiple selection or not unhighlighting a selected item we don't perform any op
  815. if (highlightedCell.highlighted && [_indexPathsForSelectedItems containsObject:indexPath]) {
  816. highlightedCell.highlighted = YES;
  817. }else {
  818. highlightedCell.highlighted = NO;
  819. }
  820. [_indexPathsForHighlightedItems removeObject:indexPath];
  821. if (notifyDelegate && _collectionViewFlags.delegateDidUnhighlightItemAtIndexPath) {
  822. [self.delegate collectionView:self didUnhighlightItemAtIndexPath:indexPath];
  823. }
  824. return YES;
  825. }
  826. return NO;
  827. }
  828. ///////////////////////////////////////////////////////////////////////////////////////////
  829. #pragma mark - Update Grid
  830. - (void)insertSections:(NSIndexSet *)sections {
  831. [self updateSections:sections updateAction:PSTCollectionUpdateActionInsert];
  832. }
  833. - (void)deleteSections:(NSIndexSet *)sections {
  834. // First delete all items
  835. NSMutableArray *paths = [NSMutableArray new];
  836. [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
  837. for (int i = 0; i < [self numberOfItemsInSection:idx]; ++i) {
  838. [paths addObject:[NSIndexPath indexPathForItem:i inSection:idx]];
  839. }
  840. }];
  841. [self deleteItemsAtIndexPaths:paths];
  842. // Then delete the section.
  843. [self updateSections:sections updateAction:PSTCollectionUpdateActionDelete];
  844. }
  845. - (void)reloadSections:(NSIndexSet *)sections {
  846. [self updateSections:sections updateAction:PSTCollectionUpdateActionReload];
  847. }
  848. - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection {
  849. NSMutableArray *moveUpdateItems = [self arrayForUpdateAction:PSTCollectionUpdateActionMove];
  850. [moveUpdateItems addObject:
  851. [[PSTCollectionViewUpdateItem alloc] initWithInitialIndexPath:[NSIndexPath indexPathForItem:NSNotFound inSection:section]
  852. finalIndexPath:[NSIndexPath indexPathForItem:NSNotFound inSection:newSection]
  853. updateAction:PSTCollectionUpdateActionMove]];
  854. if (!_collectionViewFlags.updating) {
  855. [self setupCellAnimations];
  856. [self endItemAnimations];
  857. }
  858. }
  859. - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths {
  860. [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionInsert];
  861. }
  862. - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths {
  863. [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionDelete];
  864. }
  865. - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths {
  866. [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionReload];
  867. }
  868. - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath {
  869. NSMutableArray *moveUpdateItems = [self arrayForUpdateAction:PSTCollectionUpdateActionMove];
  870. [moveUpdateItems addObject:
  871. [[PSTCollectionViewUpdateItem alloc] initWithInitialIndexPath:indexPath
  872. finalIndexPath:newIndexPath
  873. updateAction:PSTCollectionUpdateActionMove]];
  874. if (!_collectionViewFlags.updating) {
  875. [self setupCellAnimations];
  876. [self endItemAnimations];
  877. }
  878. }
  879. - (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion {
  880. [self setupCellAnimations];
  881. if (updates) updates();
  882. if (completion) _updateCompletionHandler = completion;
  883. [self endItemAnimations];
  884. }
  885. ///////////////////////////////////////////////////////////////////////////////////////////
  886. #pragma mark - Properties
  887. - (void)setBackgroundView:(UIView *)backgroundView {
  888. if (backgroundView != _backgroundView) {
  889. [_backgroundView removeFromSuperview];
  890. _backgroundView = backgroundView;
  891. backgroundView.frame = (CGRect){.origin=self.contentOffset, .size=self.bounds.size};
  892. backgroundView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
  893. [self addSubview:backgroundView];
  894. [self sendSubviewToBack:backgroundView];
  895. }
  896. }
  897. - (void)setCollectionViewLayout:(PSTCollectionViewLayout *)layout animated:(BOOL)animated {
  898. if (layout == _layout) return;
  899. // not sure it was it original code, but here this prevents crash
  900. // in case we switch layout before previous one was initially loaded
  901. if (CGRectIsEmpty(self.bounds) || !_collectionViewFlags.doneFirstLayout) {
  902. _layout.collectionView = nil;
  903. _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
  904. layout.collectionView = self;
  905. _layout = layout;
  906. // originally the use method
  907. // _setNeedsVisibleCellsUpdate:withLayoutAttributes:
  908. // here with CellsUpdate set to YES and LayoutAttributes parameter set to NO
  909. // inside this method probably some flags are set and finally
  910. // setNeedsDisplay is called
  911. _collectionViewFlags.scheduledUpdateVisibleCells = YES;
  912. _collectionViewFlags.scheduledUpdateVisibleCellLayoutAttributes = NO;
  913. [self setNeedsDisplay];
  914. }
  915. else {
  916. layout.collectionView = self;
  917. _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
  918. [_collectionViewData prepareToLoadData];
  919. NSArray *previouslySelectedIndexPaths = [self indexPathsForSelectedItems];
  920. NSMutableSet *selectedCellKeys = [NSMutableSet setWithCapacity:previouslySelectedIndexPaths.count];
  921. for (NSIndexPath *indexPath in previouslySelectedIndexPaths) {
  922. [selectedCellKeys addObject:[PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath]];
  923. }
  924. NSArray *previouslyVisibleItemsKeys = [_allVisibleViewsDict allKeys];
  925. NSSet *previouslyVisibleItemsKeysSet = [NSSet setWithArray:previouslyVisibleItemsKeys];
  926. NSMutableSet *previouslyVisibleItemsKeysSetMutable = [NSMutableSet setWithArray:previouslyVisibleItemsKeys];
  927. if ([selectedCellKeys intersectsSet:selectedCellKeys]) {
  928. [previouslyVisibleItemsKeysSetMutable intersectSet:previouslyVisibleItemsKeysSetMutable];
  929. }
  930. [self bringSubviewToFront:_allVisibleViewsDict[[previouslyVisibleItemsKeysSetMutable anyObject]]];
  931. CGPoint targetOffset = self.contentOffset;
  932. CGPoint centerPoint = CGPointMake(self.bounds.origin.x + self.bounds.size.width / 2.f,
  933. self.bounds.origin.y + self.bounds.size.height / 2.f);
  934. NSIndexPath *centerItemIndexPath = [self indexPathForItemAtPoint:centerPoint];
  935. if (!centerItemIndexPath) {
  936. NSArray *visibleItems = [self indexPathsForVisibleItems];
  937. if (visibleItems.count > 0) {
  938. centerItemIndexPath = visibleItems[visibleItems.count / 2];
  939. }
  940. }
  941. if (centerItemIndexPath) {
  942. PSTCollectionViewLayoutAttributes *layoutAttributes = [layout layoutAttributesForItemAtIndexPath:centerItemIndexPath];
  943. if (layoutAttributes) {
  944. PSTCollectionViewScrollPosition scrollPosition = PSTCollectionViewScrollPositionCenteredVertically|PSTCollectionViewScrollPositionCenteredHorizontally;
  945. CGRect targetRect = [self makeRect:layoutAttributes.frame toScrollPosition:scrollPosition];
  946. targetOffset = CGPointMake(fmax(0.f, targetRect.origin.x), fmax(0.f, targetRect.origin.y));
  947. }
  948. }
  949. CGRect newlyBounds = CGRectMake(targetOffset.x, targetOffset.y, self.bounds.size.width, self.bounds.size.height);
  950. NSArray *newlyVisibleLayoutAttrs = [_collectionViewData layoutAttributesForElementsInRect:newlyBounds];
  951. NSMutableDictionary *layoutInterchangeData = [NSMutableDictionary dictionaryWithCapacity:
  952. newlyVisibleLayoutAttrs.count + previouslyVisibleItemsKeysSet.count];
  953. NSMutableSet *newlyVisibleItemsKeys = [NSMutableSet set];
  954. for (PSTCollectionViewLayoutAttributes *attr in newlyVisibleLayoutAttrs) {
  955. PSTCollectionViewItemKey *newKey = [PSTCollectionViewItemKey collectionItemKeyForLayoutAttributes:attr];
  956. [newlyVisibleItemsKeys addObject:newKey];
  957. PSTCollectionViewLayoutAttributes *prevAttr = nil;
  958. PSTCollectionViewLayoutAttributes *newAttr = nil;
  959. if (newKey.type == PSTCollectionViewItemTypeDecorationView) {
  960. prevAttr = [self.collectionViewLayout layoutAttributesForDecorationViewOfKind:attr.representedElementKind
  961. atIndexPath:newKey.indexPath];
  962. newAttr = [layout layoutAttributesForDecorationViewOfKind:attr.representedElementKind
  963. atIndexPath:newKey.indexPath];
  964. }
  965. else if (newKey.type == PSTCollectionViewItemTypeCell) {
  966. prevAttr = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:newKey.indexPath];
  967. newAttr = [layout layoutAttributesForItemAtIndexPath:newKey.indexPath];
  968. }
  969. else {
  970. prevAttr = [self.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:attr.representedElementKind
  971. atIndexPath:newKey.indexPath];
  972. newAttr = [layout layoutAttributesForSupplementaryViewOfKind:attr.representedElementKind
  973. atIndexPath:newKey.indexPath];
  974. }
  975. if (prevAttr != nil && newAttr != nil) {
  976. layoutInterchangeData[newKey] = @{@"previousLayoutInfos": prevAttr, @"newLayoutInfos": newAttr};
  977. }
  978. }
  979. for (PSTCollectionViewItemKey *key in previouslyVisibleItemsKeysSet) {
  980. PSTCollectionViewLayoutAttributes *prevAttr = nil;
  981. PSTCollectionViewLayoutAttributes *newAttr = nil;
  982. if (key.type == PSTCollectionViewItemTypeDecorationView) {
  983. PSTCollectionReusableView *decorView = _allVisibleViewsDict[key];
  984. prevAttr = [self.collectionViewLayout layoutAttributesForDecorationViewOfKind:decorView.reuseIdentifier
  985. atIndexPath:key.indexPath];
  986. newAttr = [layout layoutAttributesForDecorationViewOfKind:decorView.reuseIdentifier
  987. atIndexPath:key.indexPath];
  988. }
  989. else if (key.type == PSTCollectionViewItemTypeCell) {
  990. prevAttr = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:key.indexPath];
  991. newAttr = [layout layoutAttributesForItemAtIndexPath:key.indexPath];
  992. }
  993. else if (key.type == PSTCollectionViewItemTypeSupplementaryView) {
  994. PSTCollectionReusableView *suuplView = _allVisibleViewsDict[key];
  995. prevAttr = [self.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:suuplView.layoutAttributes.representedElementKind
  996. atIndexPath:key.indexPath];
  997. newAttr = [layout layoutAttributesForSupplementaryViewOfKind:suuplView.layoutAttributes.representedElementKind
  998. atIndexPath:key.indexPath];
  999. }
  1000. NSMutableDictionary *layoutInterchangeDataValue = [NSMutableDictionary dictionary];
  1001. if (prevAttr) layoutInterchangeDataValue[@"previousLayoutInfos"] = prevAttr;
  1002. if (newAttr) layoutInterchangeDataValue[@"newLayoutInfos"] = newAttr;
  1003. layoutInterchangeData[key] = layoutInterchangeDataValue;
  1004. }
  1005. for (PSTCollectionViewItemKey *key in [layoutInterchangeData keyEnumerator]) {
  1006. if (key.type == PSTCollectionViewItemTypeCell) {
  1007. PSTCollectionViewCell *cell = _allVisibleViewsDict[key];
  1008. if (!cell) {
  1009. cell = [self createPreparedCellForItemAtIndexPath:key.indexPath
  1010. withLayoutAttributes:layoutInterchangeData[key][@"previousLayoutInfos"]];
  1011. _allVisibleViewsDict[key] = cell;
  1012. [self addControlledSubview:cell];
  1013. }
  1014. else [cell applyLayoutAttributes:layoutInterchangeData[key][@"previousLayoutInfos"]];
  1015. }
  1016. else if (key.type == PSTCollectionViewItemTypeSupplementaryView) {
  1017. PSTCollectionReusableView *view = _allVisibleViewsDict[key];
  1018. if (!view) {
  1019. PSTCollectionViewLayoutAttributes *attrs = layoutInterchangeData[key][@"previousLayoutInfos"];
  1020. view = [self createPreparedSupplementaryViewForElementOfKind:attrs.representedElementKind
  1021. atIndexPath:attrs.indexPath
  1022. withLayoutAttributes:attrs];
  1023. _allVisibleViewsDict[key] = view;
  1024. [self addControlledSubview:view];
  1025. }
  1026. }
  1027. else if (key.type == PSTCollectionViewItemTypeDecorationView) {
  1028. PSTCollectionReusableView *view = _allVisibleViewsDict[key];
  1029. if (!view) {
  1030. PSTCollectionViewLayoutAttributes *attrs = layoutInterchangeData[key][@"previousLayoutInfos"];
  1031. view = [self dequeueReusableOrCreateDecorationViewOfKind:attrs.representedElementKind forIndexPath:attrs.indexPath];
  1032. _allVisibleViewsDict[key] = view;
  1033. [self addControlledSubview:view];
  1034. }
  1035. }
  1036. };
  1037. CGRect contentRect = [_collectionViewData collectionViewContentRect];
  1038. void (^applyNewLayoutBlock)(void) = ^{
  1039. NSEnumerator *keys = [layoutInterchangeData keyEnumerator];
  1040. for (PSTCollectionViewItemKey *key in keys) {
  1041. // TODO: This is most likely not 100% the same time as in UICollectionView. Needs to be investigated.
  1042. PSTCollectionViewCell *cell = (PSTCollectionViewCell *)self->_allVisibleViewsDict[key];
  1043. [cell willTransitionFromLayout:self->_layout toLayout:layout];
  1044. [cell applyLayoutAttributes:layoutInterchangeData[key][@"newLayoutInfos"]];
  1045. [cell didTransitionFromLayout:self->_layout toLayout:layout];
  1046. }
  1047. };
  1048. void (^freeUnusedViews)(void) = ^{
  1049. NSMutableSet *toRemove = [NSMutableSet set];
  1050. for (PSTCollectionViewItemKey *key in [self->_allVisibleViewsDict keyEnumerator]) {
  1051. if (![newlyVisibleItemsKeys containsObject:key]) {
  1052. if (key.type == PSTCollectionViewItemTypeCell) {
  1053. [self reuseCell:self->_allVisibleViewsDict[key]];
  1054. [toRemove addObject:key];
  1055. }
  1056. else if (key.type == PSTCollectionViewItemTypeSupplementaryView) {
  1057. [self reuseSupplementaryView:self->_allVisibleViewsDict[key]];
  1058. [toRemove addObject:key];
  1059. }
  1060. else if (key.type == PSTCollectionViewItemTypeDecorationView) {
  1061. [self reuseDecorationView:self->_allVisibleViewsDict[key]];
  1062. [toRemove addObject:key];
  1063. }
  1064. }
  1065. }
  1066. for (id key in toRemove)
  1067. [self->_allVisibleViewsDict removeObjectForKey:key];
  1068. };
  1069. if (animated) {
  1070. [UIView animateWithDuration:.3 animations:^{
  1071. self->_collectionViewFlags.updatingLayout = YES;
  1072. self.contentOffset = targetOffset;
  1073. self.contentSize = contentRect.size;
  1074. applyNewLayoutBlock();
  1075. } completion:^(BOOL finished) {
  1076. freeUnusedViews();
  1077. self->_collectionViewFlags.updatingLayout = NO;
  1078. // layout subviews for updating content offset or size while updating layout
  1079. if (!CGPointEqualToPoint(self.contentOffset, targetOffset)
  1080. || !CGSizeEqualToSize(self.contentSize, contentRect.size)) {
  1081. [self layoutSubviews];
  1082. }
  1083. }];
  1084. }
  1085. else {
  1086. self.contentOffset = targetOffset;
  1087. self.contentSize = contentRect.size;
  1088. applyNewLayoutBlock();
  1089. freeUnusedViews();
  1090. }
  1091. _layout.collectionView = nil;
  1092. _layout = layout;
  1093. }
  1094. }
  1095. - (void)setCollectionViewLayout:(PSTCollectionViewLayout *)layout {
  1096. [self setCollectionViewLayout:layout animated:NO];
  1097. }
  1098. - (id<PSTCollectionViewDelegate>)delegate {
  1099. return self.extVars.collectionViewDelegate;
  1100. }
  1101. - (void)setDelegate:(id<PSTCollectionViewDelegate>)delegate {
  1102. self.extVars.collectionViewDelegate = delegate;
  1103. // Managing the Selected Cells
  1104. _collectionViewFlags.delegateShouldSelectItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:shouldSelectItemAtIndexPath:)];
  1105. _collectionViewFlags.delegateDidSelectItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)];
  1106. _collectionViewFlags.delegateShouldDeselectItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:shouldDeselectItemAtIndexPath:)];
  1107. _collectionViewFlags.delegateDidDeselectItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:didDeselectItemAtIndexPath:)];
  1108. // Managing Cell Highlighting
  1109. _collectionViewFlags.delegateShouldHighlightItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:shouldHighlightItemAtIndexPath:)];
  1110. _collectionViewFlags.delegateDidHighlightItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:didHighlightItemAtIndexPath:)];
  1111. _collectionViewFlags.delegateDidUnhighlightItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:)];
  1112. // Tracking the Removal of Views
  1113. _collectionViewFlags.delegateDidEndDisplayingCell = [self.delegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)];
  1114. _collectionViewFlags.delegateDidEndDisplayingSupplementaryView = [self.delegate respondsToSelector:@selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:)];
  1115. // Managing Actions for Cells
  1116. _collectionViewFlags.delegateSupportsMenus = [self.delegate respondsToSelector:@selector(collectionView:shouldShowMenuForItemAtIndexPath:)];
  1117. // These aren't present in the flags which is a little strange. Not adding them because that will mess with byte alignment which will affect cross compatibility.
  1118. // The flag names are guesses and are there for documentation purposes.
  1119. // _collectionViewFlags.delegateCanPerformActionForItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:canPerformAction:forItemAtIndexPath:withSender:)];
  1120. // _collectionViewFlags.delegatePerformActionForItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:performAction:forItemAtIndexPath:withSender:)];
  1121. }
  1122. // Might be overkill since two are required and two are handled by PSTCollectionViewData leaving only one flag we actually need to check for
  1123. - (void)setDataSource:(id<PSTCollectionViewDataSource>)dataSource {
  1124. if (dataSource != _dataSource) {
  1125. _dataSource = dataSource;
  1126. // Getting Item and Section Metrics
  1127. _collectionViewFlags.dataSourceNumberOfSections = [_dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
  1128. // Getting Views for Items
  1129. _collectionViewFlags.dataSourceViewForSupplementaryElement = [_dataSource respondsToSelector:@selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:)];
  1130. }
  1131. }
  1132. - (BOOL)allowsSelection {
  1133. return _collectionViewFlags.allowsSelection;
  1134. }
  1135. - (void)setAllowsSelection:(BOOL)allowsSelection {
  1136. _collectionViewFlags.allowsSelection = allowsSelection;
  1137. }
  1138. - (BOOL)allowsMultipleSelection {
  1139. return _collectionViewFlags.allowsMultipleSelection;
  1140. }
  1141. - (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection {
  1142. _collectionViewFlags.allowsMultipleSelection = allowsMultipleSelection;
  1143. // Deselect all objects if allows multiple selection is false
  1144. if (!allowsMultipleSelection && _indexPathsForSelectedItems.count) {
  1145. // Note: Apple's implementation leaves a mostly random item selected. Presumably they
  1146. // have a good reason for this, but I guess it's just skipping the last or first index.
  1147. for (NSIndexPath *selectedIndexPath in [_indexPathsForSelectedItems copy]) {
  1148. if (_indexPathsForSelectedItems.count == 1) continue;
  1149. [self deselectItemAtIndexPath:selectedIndexPath animated:YES notifyDelegate:YES];
  1150. }
  1151. }
  1152. }
  1153. - (CGRect)visibleBoundRects {
  1154. // in original UICollectionView implementation they
  1155. // check for _visibleBounds and can union self.bounds
  1156. // with this value. Don't know the meaning of _visibleBounds however.
  1157. return self.bounds;
  1158. }
  1159. ///////////////////////////////////////////////////////////////////////////////////////////
  1160. #pragma mark - Private
  1161. - (PSTCollectionViewExt *)extVars {
  1162. return objc_getAssociatedObject(self, &kPSTColletionViewExt);
  1163. }
  1164. - (void)invalidateLayout {
  1165. [self.collectionViewLayout invalidateLayout];
  1166. [self.collectionViewData invalidate]; // invalidate layout cache
  1167. }
  1168. // update currently visible cells, fetches new cells if needed
  1169. // TODO: use now parameter.
  1170. - (void)updateVisibleCellsNow:(BOOL)now {
  1171. NSArray *layoutAttributesArray = [_collectionViewData layoutAttributesForElementsInRect:self.bounds];
  1172. if (layoutAttributesArray == nil || layoutAttributesArray.count == 0) {
  1173. // If our layout source isn't providing any layout information, we should just
  1174. // stop, otherwise we'll blow away all the currently existing cells.
  1175. return;
  1176. }
  1177. // create ItemKey/Attributes dictionary
  1178. NSMutableDictionary *itemKeysToAddDict = [NSMutableDictionary dictionary];
  1179. // Add new cells.
  1180. for (PSTCollectionViewLayoutAttributes *layoutAttributes in layoutAttributesArray) {
  1181. PSTCollectionViewItemKey *itemKey = [PSTCollectionViewItemKey collectionItemKeyForLayoutAttributes:layoutAttributes];
  1182. itemKeysToAddDict[itemKey] = layoutAttributes;
  1183. // check if cell is in visible dict; add it if not.
  1184. PSTCollectionReusableView *view = _allVisibleViewsDict[itemKey];
  1185. if (!view) {
  1186. if (itemKey.type == PSTCollectionViewItemTypeCell) {
  1187. view = [self createPreparedCellForItemAtIndexPath:itemKey.indexPath withLayoutAttributes:layoutAttributes];
  1188. }else if (itemKey.type == PSTCollectionViewItemTypeSupplementaryView) {
  1189. view = [self createPreparedSupplementaryViewForElementOfKind:layoutAttributes.representedElementKind
  1190. atIndexPath:layoutAttributes.indexPath
  1191. withLayoutAttributes:layoutAttributes];
  1192. }else if (itemKey.type == PSTCollectionViewItemTypeDecorationView) {
  1193. view = [self dequeueReusableOrCreateDecorationViewOfKind:layoutAttributes.representedElementKind forIndexPath:layoutAttributes.indexPath];
  1194. }
  1195. // Supplementary views are optional
  1196. if (view) {
  1197. _allVisibleViewsDict[itemKey] = view;
  1198. [self addControlledSubview:view];
  1199. // Always apply attributes. Fixes #203.
  1200. [view applyLayoutAttributes:layoutAttributes];
  1201. }
  1202. }else {
  1203. // just update cell
  1204. [view applyLayoutAttributes:layoutAttributes];
  1205. }
  1206. }
  1207. // Detect what items should be removed and queued back.
  1208. NSMutableSet *allVisibleItemKeys = [NSMutableSet setWithArray:[_allVisibleViewsDict allKeys]];
  1209. [allVisibleItemKeys minusSet:[NSSet setWithArray:[itemKeysToAddDict allKeys]]];
  1210. // Finally remove views that have not been processed and prepare them for re-use.
  1211. for (PSTCollectionViewItemKey *itemKey in allVisibleItemKeys) {
  1212. PSTCollectionReusableView *reusableView = _allVisibleViewsDict[itemKey];
  1213. if (reusableView) {
  1214. [reusableView removeFromSuperview];
  1215. [_allVisibleViewsDict removeObjectForKey:itemKey];
  1216. if (itemKey.type == PSTCollectionViewItemTypeCell) {
  1217. if (_collectionViewFlags.delegateDidEndDisplayingCell) {
  1218. [self.delegate collectionView:self didEndDisplayingCell:(PSTCollectionViewCell *)reusableView forItemAtIndexPath:itemKey.indexPath];
  1219. }
  1220. [self reuseCell:(PSTCollectionViewCell *)reusableView];
  1221. }
  1222. else if (itemKey.type == PSTCollectionViewItemTypeSupplementaryView) {
  1223. if (_collectionViewFlags.delegateDidEndDisplayingSupplementaryView) {
  1224. [self.delegate collectionView:self didEndDisplayingSupplementaryView:reusableView forElementOfKind:itemKey.identifier atIndexPath:itemKey.indexPath];
  1225. }
  1226. [self reuseSupplementaryView:reusableView];
  1227. }
  1228. else if (itemKey.type == PSTCollectionViewItemTypeDecorationView) {
  1229. [self reuseDecorationView:reusableView];
  1230. }
  1231. }
  1232. }
  1233. }
  1234. // fetches a cell from the dataSource and sets the layoutAttributes
  1235. - (PSTCollectionViewCell *)createPreparedCellForItemAtIndexPath:(NSIndexPath *)indexPath withLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes {
  1236. PSTCollectionViewCell *cell = [self.dataSource collectionView:self cellForItemAtIndexPath:indexPath];
  1237. // Apply attributes
  1238. [cell applyLayoutAttributes:layoutAttributes];
  1239. // reset selected/highlight state
  1240. [cell setHighlighted:[_indexPathsForHighlightedItems containsObject:indexPath]];
  1241. [cell setSelected:[_indexPathsForSelectedItems containsObject:indexPath]];
  1242. // voiceover support
  1243. cell.isAccessibilityElement = YES;
  1244. return cell;
  1245. }
  1246. - (PSTCollectionReusableView *)createPreparedSupplementaryViewForElementOfKind:(NSString *)kind
  1247. atIndexPath:(NSIndexPath *)indexPath
  1248. withLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes {
  1249. if (_collectionViewFlags.dataSourceViewForSupplementaryElement) {
  1250. PSTCollectionReusableView *view = [self.dataSource collectionView:self
  1251. viewForSupplementaryElementOfKind:kind
  1252. atIndexPath:indexPath];
  1253. [view applyLayoutAttributes:layoutAttributes];
  1254. return view;
  1255. }
  1256. return nil;
  1257. }
  1258. // @steipete optimization
  1259. - (void)queueReusableView:(PSTCollectionReusableView *)reusableView inQueue:(NSMutableDictionary *)queue withIdentifier:(NSString *)identifier {
  1260. NSParameterAssert(identifier.length > 0);
  1261. [reusableView removeFromSuperview];
  1262. [reusableView prepareForReuse];
  1263. // enqueue cell
  1264. NSMutableArray *reuseableViews = queue[identifier];
  1265. if (!reuseableViews) {
  1266. reuseableViews = [NSMutableArray array];
  1267. queue[identifier] = reuseableViews;
  1268. }
  1269. [reuseableViews addObject:reusableView];
  1270. }
  1271. // enqueue cell for reuse
  1272. - (void)reuseCell:(PSTCollectionViewCell *)cell {
  1273. [self queueReusableView:cell inQueue:_cellReuseQueues withIdentifier:cell.reuseIdentifier];
  1274. }
  1275. // enqueue supplementary view for reuse
  1276. - (void)reuseSupplementaryView:(PSTCollectionReusableView *)supplementaryView {
  1277. NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", supplementaryView.layoutAttributes.elementKind, supplementaryView.reuseIdentifier];
  1278. [self queueReusableView:supplementaryView inQueue:_supplementaryViewReuseQueues withIdentifier:kindAndIdentifier];
  1279. }
  1280. // enqueue decoration view for reuse
  1281. - (void)reuseDecorationView:(PSTCollectionReusableView *)decorationView {
  1282. [self queueReusableView:decorationView inQueue:_decorationViewReuseQueues withIdentifier:decorationView.reuseIdentifier];
  1283. }
  1284. - (void)addControlledSubview:(PSTCollectionReusableView *)subview {
  1285. // avoids placing views above the scroll indicator
  1286. // If the collection view is not displaying scrollIndicators then self.subviews.count can be 0.
  1287. // We take the max to ensure we insert at a non negative index because a negative index will silently fail to insert the view
  1288. NSInteger insertionIndex = MAX((NSInteger)(self.subviews.count - (self.dragging ? 1 : 0)), 0);
  1289. [self insertSubview:subview atIndex:insertionIndex];
  1290. UIView *scrollIndicatorView = nil;
  1291. if (self.dragging) {
  1292. scrollIndicatorView = [self.subviews lastObject];
  1293. }
  1294. NSMutableArray *floatingViews = [[NSMutableArray alloc] init];
  1295. for (UIView *uiView in self.subviews) {
  1296. if ([uiView isKindOfClass:PSTCollectionReusableView.class] && [[(PSTCollectionReusableView *)uiView layoutAttributes] zIndex] > 0) {
  1297. [floatingViews addObject:uiView];
  1298. }
  1299. }
  1300. [floatingViews sortUsingComparator:^NSComparisonResult(PSTCollectionReusableView *obj1, PSTCollectionReusableView *obj2) {
  1301. CGFloat z1 = [[obj1 layoutAttributes] zIndex];
  1302. CGFloat z2 = [[obj2 layoutAttributes] zIndex];
  1303. if (z1 > z2) {
  1304. return (NSComparisonResult)NSOrderedDescending;
  1305. }else if (z1 < z2) {
  1306. return (NSComparisonResult)NSOrderedAscending;
  1307. }else {
  1308. return (NSComparisonResult)NSOrderedSame;
  1309. }
  1310. }];
  1311. for (PSTCollectionReusableView *uiView in floatingViews) {
  1312. [self bringSubviewToFront:uiView];
  1313. }
  1314. if (floatingViews.count && scrollIndicatorView) {
  1315. [self bringSubviewToFront:scrollIndicatorView];
  1316. }
  1317. }
  1318. ///////////////////////////////////////////////////////////////////////////////////////////
  1319. #pragma mark - Updating grid internal functionality
  1320. - (void)suspendReloads {
  1321. _reloadingSuspendedCount++;
  1322. }
  1323. - (void)resumeReloads {
  1324. if (0 < _reloadingSuspendedCount) _reloadingSuspendedCount--;
  1325. }
  1326. - (NSMutableArray *)arrayForUpdateAction:(PSTCollectionUpdateAction)updateAction {
  1327. NSMutableArray *updateActions = nil;
  1328. switch (updateAction) {
  1329. case PSTCollectionUpdateActionInsert:
  1330. if (!_insertItems) _insertItems = [NSMutableArray new];
  1331. updateActions = _insertItems;
  1332. break;
  1333. case PSTCollectionUpdateActionDelete:
  1334. if (!_deleteItems) _deleteItems = [NSMutableArray new];
  1335. updateActions = _deleteItems;
  1336. break;
  1337. case PSTCollectionUpdateActionMove:
  1338. if (!_moveItems) _moveItems = [NSMutableArray new];
  1339. updateActions = _moveItems;
  1340. break;
  1341. case PSTCollectionUpdateActionReload:
  1342. if (!_reloadItems) _reloadItems = [NSMutableArray new];
  1343. updateActions = _reloadItems;
  1344. break;
  1345. default: break;
  1346. }
  1347. return updateActions;
  1348. }
  1349. - (void)prepareLayoutForUpdates {
  1350. NSMutableArray *array = [[NSMutableArray alloc] init];
  1351. [array addObjectsFromArray:[_originalDeleteItems sortedArrayUsingSelector:@selector(inverseCompareIndexPaths:)]];
  1352. [array addObjectsFromArray:[_originalInsertItems sortedArrayUsingSelector:@selector(compareIndexPaths:)]];
  1353. [array addObjectsFromArray:[_reloadItems sortedArrayUsingSelector:@selector(compareIndexPaths:)]];
  1354. [array addObjectsFromArray:[_moveItems sortedArrayUsingSelector:@selector(compareIndexPaths:)]];
  1355. [_layout prepareForCollectionViewUpdates:array];
  1356. }
  1357. - (void)updateWithItems:(NSArray *)items {
  1358. [self prepareLayoutForUpdates];
  1359. NSMutableArray *animations = [[NSMutableArray alloc] init];
  1360. NSMutableDictionary *newAllVisibleView = [[NSMutableDictionary alloc] init];
  1361. NSMutableDictionary *viewsToRemove = [NSMutableDictionary dictionaryWithObjectsAndKeys:
  1362. [NSMutableArray array], @(PSTCollectionViewItemTypeCell),
  1363. [NSMutableArray array], @(PSTCollectionViewItemTypeDecorationView),
  1364. [NSMutableArray array], @(PSTCollectionViewItemTypeSupplementaryView), nil];
  1365. for (PSTCollectionViewUpdateItem *updateItem in items) {
  1366. if (updateItem.isSectionOperation && updateItem.updateAction != PSTCollectionUpdateActionDelete) continue;
  1367. if (updateItem.isSectionOperation && updateItem.updateAction == PSTCollectionUpdateActionDelete) {
  1368. NSInteger numberOfBeforeSection = [_update[@"oldModel"] numberOfItemsInSection:updateItem.indexPathBeforeUpdate.section];
  1369. for (NSInteger i = 0; i < numberOfBeforeSection; i++) {
  1370. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:updateItem.indexPathBeforeUpdate.section];
  1371. PSTCollectionViewLayoutAttributes *finalAttrs = [_layout finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
  1372. PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath];
  1373. PSTCollectionReusableView *view = _allVisibleViewsDict[key];
  1374. if (view) {
  1375. PSTCollectionViewLayoutAttributes *startAttrs = view.layoutAttributes;
  1376. if (!finalAttrs) {
  1377. finalAttrs = [startAttrs copy];
  1378. finalAttrs.alpha = 0;
  1379. }
  1380. [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs, @"newLayoutInfos" : finalAttrs}];
  1381. [_allVisibleViewsDict removeObjectForKey:key];
  1382. [(NSMutableArray *)viewsToRemove[@(key.type)] addObject:view];
  1383. }
  1384. }
  1385. continue;
  1386. }
  1387. if (updateItem.updateAction == PSTCollectionUpdateActionDelete) {
  1388. NSIndexPath *indexPath = updateItem.indexPathBeforeUpdate;
  1389. PSTCollectionViewLayoutAttributes *finalAttrs = [_layout finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
  1390. PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath];
  1391. PSTCollectionReusableView *view = _allVisibleViewsDict[key];
  1392. if (view) {
  1393. PSTCollectionViewLayoutAttributes *startAttrs = view.layoutAttributes;
  1394. if (!finalAttrs) {
  1395. finalAttrs = [startAttrs copy];
  1396. finalAttrs.alpha = 0;
  1397. }
  1398. [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs, @"newLayoutInfos" : finalAttrs}];
  1399. [_allVisibleViewsDict removeObjectForKey:key];
  1400. [(NSMutableArray *)viewsToRemove[@(key.type)] addObject:view];
  1401. }
  1402. }
  1403. else if (updateItem.updateAction == PSTCollectionUpdateActionInsert) {
  1404. NSIndexPath *indexPath = updateItem.indexPathAfterUpdate;
  1405. PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath];
  1406. PSTCollectionViewLayoutAttributes *startAttrs = [_layout initialLayoutAttributesForAppearingItemAtIndexPath:indexPath];
  1407. PSTCollectionViewLayoutAttributes *finalAttrs = [_layout layoutAttributesForItemAtIndexPath:indexPath];
  1408. CGRect startRect = startAttrs.frame;
  1409. CGRect finalRect = finalAttrs.frame;
  1410. if (CGRectIntersectsRect(self.visibleBoundRects, startRect) || CGRectIntersectsRect(self.visibleBoundRects, finalRect)) {
  1411. if (!startAttrs) {
  1412. startAttrs = [finalAttrs copy];
  1413. startAttrs.alpha = 0;
  1414. }
  1415. PSTCollectionReusableView *view = [self createPreparedCellForItemAtIndexPath:indexPath
  1416. withLayoutAttributes:startAttrs];
  1417. [self addControlledSubview:view];
  1418. newAllVisibleView[key] = view;
  1419. [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs, @"newLayoutInfos" : finalAttrs}];
  1420. }
  1421. }
  1422. else if (updateItem.updateAction == PSTCollectionUpdateActionMove) {
  1423. NSIndexPath *indexPathBefore = updateItem.indexPathBeforeUpdate;
  1424. NSIndexPath *indexPathAfter = updateItem.indexPathAfterUpdate;
  1425. PSTCollectionViewItemKey *keyBefore = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPathBefore];
  1426. PSTCollectionViewItemKey *keyAfter = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPathAfter];
  1427. PSTCollectionReusableView *view = _allVisibleViewsDict[keyBefore];
  1428. PSTCollectionViewLayoutAttributes *startAttrs = nil;
  1429. PSTCollectionViewLayoutAttributes *finalAttrs = [_layout layoutAttributesForItemAtIndexPath:indexPathAfter];
  1430. if (view) {
  1431. startAttrs = view.layoutAttributes;
  1432. [_allVisibleViewsDict removeObjectForKey:keyBefore];
  1433. newAllVisibleView[keyAfter] = view;
  1434. }
  1435. else {
  1436. startAttrs = [finalAttrs copy];
  1437. startAttrs.alpha = 0;
  1438. view = [self createPreparedCellForItemAtIndexPath:indexPathAfter withLayoutAttributes:startAttrs];
  1439. [self addControlledSubview:view];
  1440. newAllVisibleView[keyAfter] = view;
  1441. }
  1442. [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs, @"newLayoutInfos" : finalAttrs}];
  1443. }
  1444. }
  1445. for (PSTCollectionViewItemKey *key in [_allVisibleViewsDict keyEnumerator]) {
  1446. PSTCollectionReusableView *view = _allVisibleViewsDict[key];
  1447. if (key.type == PSTCollectionViewItemTypeCell) {
  1448. NSUInteger oldGlobalIndex = [_update[@"oldModel"] globalIndexForItemAtIndexPath:key.indexPath];
  1449. NSArray *oldToNewIndexMap = _update[@"oldToNewIndexMap"];
  1450. NSUInteger newGlobalIndex = NSNotFound;
  1451. if (NSNotFound != oldGlobalIndex && oldGlobalIndex < oldToNewIndexMap.count) {
  1452. newGlobalIndex = [oldToNewIndexMap[oldGlobalIndex] intValue];
  1453. }
  1454. NSIndexPath *newIndexPath = newGlobalIndex == NSNotFound ? nil : [_update[@"newModel"] indexPathForItemAtGlobalIndex:newGlobalIndex];
  1455. NSIndexPath *oldIndexPath = oldGlobalIndex == NSNotFound ? nil : [_update[@"oldModel"] indexPathForItemAtGlobalIndex:oldGlobalIndex];
  1456. if (newIndexPath) {
  1457. PSTCollectionViewLayoutAttributes *startAttrs = nil;
  1458. PSTCollectionViewLayoutAttributes *finalAttrs = nil;
  1459. startAttrs = [_layout initialLayoutAttributesForAppearingItemAtIndexPath:oldIndexPath];
  1460. finalAttrs = [_layout layoutAttributesForItemAtIndexPath:newIndexPath];
  1461. NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:@{@"view" : view}];
  1462. if (startAttrs) dic[@"previousLayoutInfos"] = startAttrs;
  1463. if (finalAttrs) dic[@"newLayoutInfos"] = finalAttrs;
  1464. [animations addObject:dic];
  1465. PSTCollectionViewItemKey *newKey = [key copy];
  1466. [newKey setIndexPath:newIndexPath];
  1467. newAllVisibleView[newKey] = view;
  1468. }
  1469. }else if (key.type == PSTCollectionViewItemTypeSupplementaryView) {
  1470. PSTCollectionViewLayoutAttributes *startAttrs = nil;
  1471. PSTCollectionViewLayoutAttributes *finalAttrs = nil;
  1472. startAttrs = view.layoutAttributes;
  1473. finalAttrs = [_layout layoutAttributesForSupplementaryViewOfKind:view.layoutAttributes.representedElementKind atIndexPath:key.indexPath];
  1474. NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:@{@"view" : view}];
  1475. if (startAttrs) dic[@"previousLayoutInfos"] = startAttrs;
  1476. if (finalAttrs) dic[@"newLayoutInfos"] = finalAttrs;
  1477. [animations addObject:dic];
  1478. PSTCollectionViewItemKey *newKey = [key copy];
  1479. newAllVisibleView[newKey] = view;
  1480. }
  1481. }
  1482. NSArray *allNewlyVisibleItems = [_layout layoutAttributesForElementsInRect:self.visibleBoundRects];
  1483. for (PSTCollectionViewLayoutAttributes *attrs in allNewlyVisibleItems) {
  1484. PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForLayoutAttributes:attrs];
  1485. if (key.type == PSTCollectionViewItemTypeCell && ![[newAllVisibleView allKeys] containsObject:key]) {
  1486. PSTCollectionViewLayoutAttributes *startAttrs =
  1487. [_layout initialLayoutAttributesForAppearingItemAtIndexPath:attrs.indexPath];
  1488. PSTCollectionReusableView *view = [self createPreparedCellForItemAtIndexPath:attrs.indexPath
  1489. withLayoutAttributes:startAttrs];
  1490. [self addControlledSubview:view];
  1491. newAllVisibleView[key] = view;
  1492. [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs ? startAttrs : attrs, @"newLayoutInfos" : attrs}];
  1493. }
  1494. }
  1495. _allVisibleViewsDict = newAllVisibleView;
  1496. for (NSDictionary *animation in animations) {
  1497. PSTCollectionReusableView *view = animation[@"view"];
  1498. PSTCollectionViewLayoutAttributes *attr = animation[@"previousLayoutInfos"];
  1499. [view applyLayoutAttributes:attr];
  1500. };
  1501. [UIView animateWithDuration:.3 animations:^{
  1502. self->_collectionViewFlags.updatingLayout = YES;
  1503. [CATransaction begin];
  1504. [CATransaction setAnimationDuration:.3];
  1505. // You might wonder why we use CATransaction to handle animation completion
  1506. // here instead of using the completion: parameter of UIView's animateWithDuration:.
  1507. // The problem is that animateWithDuration: calls this completion block
  1508. // when other animations are finished. This means that the block is called
  1509. // after the user releases his finger and the scroll view has finished scrolling.
  1510. // This can be a large delay, which causes the layout of the cells to be greatly
  1511. // delayed, and thus, be unrendered. I assume that was done for performance
  1512. // purposes but it completely breaks our layout logic here.
  1513. // To get the completion block called immediately after the animation actually
  1514. // finishes, I switched to use CATransaction.
  1515. // The only thing I'm not sure about - _completed_ flag. I don't know where to get it
  1516. // in terms of CATransaction's API, so I use animateWithDuration's completion block
  1517. // to call _updateCompletionHandler with that flag.
  1518. // Ideally, _updateCompletionHandler should be called along with the other logic in
  1519. // CATransaction's completionHandler but I simply don't know where to get that flag.
  1520. [CATransaction setCompletionBlock:^{
  1521. // Iterate through all the views that we are going to remove.
  1522. [viewsToRemove enumerateKeysAndObjectsUsingBlock:^(NSNumber *keyObj, NSArray *views, BOOL *stop) {
  1523. PSTCollectionViewItemType type = [keyObj unsignedIntegerValue];
  1524. for (PSTCollectionReusableView *view in views) {
  1525. if (type == PSTCollectionViewItemTypeCell) {
  1526. [self reuseCell:(PSTCollectionViewCell *)view];
  1527. }else if (type == PSTCollectionViewItemTypeSupplementaryView) {
  1528. [self reuseSupplementaryView:view];
  1529. }else if (type == PSTCollectionViewItemTypeDecorationView) {
  1530. [self reuseDecorationView:view];
  1531. }
  1532. }
  1533. }];
  1534. self->_collectionViewFlags.updatingLayout = NO;
  1535. }];
  1536. for (NSDictionary *animation in animations) {
  1537. PSTCollectionReusableView *view = animation[@"view"];
  1538. PSTCollectionViewLayoutAttributes *attrs = animation[@"newLayoutInfos"];
  1539. [view applyLayoutAttributes:attrs];
  1540. }
  1541. [CATransaction commit];
  1542. } completion:^(BOOL finished) {
  1543. if (self->_updateCompletionHandler) {
  1544. self->_updateCompletionHandler(finished);
  1545. self->_updateCompletionHandler = nil;
  1546. }
  1547. }];
  1548. [_layout finalizeCollectionViewUpdates];
  1549. }
  1550. - (void)setupCellAnimations {
  1551. [self updateVisibleCellsNow:YES];
  1552. [self suspendReloads];
  1553. _collectionViewFlags.updating = YES;
  1554. }
  1555. - (void)endItemAnimations {
  1556. _updateCount++;
  1557. PSTCollectionViewData *oldCollectionViewData = _collectionViewData;
  1558. _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:_layout];
  1559. [_layout invalidateLayout];
  1560. [_collectionViewData prepareToLoadData];
  1561. NSMutableArray *someMutableArr1 = [[NSMutableArray alloc] init];
  1562. NSArray *removeUpdateItems = [[self arrayForUpdateAction:PSTCollectionUpdateActionDelete]
  1563. sortedArrayUsingSelector:@selector(inverseCompareIndexPaths:)];
  1564. NSArray *insertUpdateItems = [[self arrayForUpdateAction:PSTCollectionUpdateActionInsert]
  1565. sortedArrayUsingSelector:@selector(compareIndexPaths:)];
  1566. NSMutableArray *sortedMutableReloadItems = [[_reloadItems sortedArrayUsingSelector:@selector(compareIndexPaths:)] mutableCopy];
  1567. NSMutableArray *sortedMutableMoveItems = [[_moveItems sortedArrayUsingSelector:@selector(compareIndexPaths:)] mutableCopy];
  1568. _originalDeleteItems = [removeUpdateItems copy];
  1569. _originalInsertItems = [insertUpdateItems copy];
  1570. NSMutableArray *someMutableArr2 = [[NSMutableArray alloc] init];
  1571. NSMutableArray *someMutableArr3 = [[NSMutableArray alloc] init];
  1572. NSMutableDictionary *operations = [[NSMutableDictionary alloc] init];
  1573. for (PSTCollectionViewUpdateItem *updateItem in sortedMutableReloadItems) {
  1574. NSAssert(updateItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
  1575. @"attempt to reload item (%@) that doesn't exist (there are only %ld sections before update)",
  1576. updateItem.indexPathBeforeUpdate, (long)[oldCollectionViewData numberOfSections]);
  1577. if (updateItem.indexPathBeforeUpdate.item != NSNotFound) {
  1578. NSAssert(updateItem.indexPathBeforeUpdate.item < [oldCollectionViewData numberOfItemsInSection:updateItem.indexPathBeforeUpdate.section],
  1579. @"attempt to reload item (%@) that doesn't exist (there are only %ld items in section %ld before update)",
  1580. updateItem.indexPathBeforeUpdate,
  1581. (long)[oldCollectionViewData numberOfItemsInSection:updateItem.indexPathBeforeUpdate.section],
  1582. (long)updateItem.indexPathBeforeUpdate.section);
  1583. }
  1584. [someMutableArr2 addObject:[[PSTCollectionViewUpdateItem alloc] initWithAction:PSTCollectionUpdateActionDelete
  1585. forIndexPath:updateItem.indexPathBeforeUpdate]];
  1586. [someMutableArr3 addObject:[[PSTCollectionViewUpdateItem alloc] initWithAction:PSTCollectionUpdateActionInsert
  1587. forIndexPath:updateItem.indexPathAfterUpdate]];
  1588. }
  1589. NSMutableArray *sortedDeletedMutableItems = [[_deleteItems sortedArrayUsingSelector:@selector(inverseCompareIndexPaths:)] mutableCopy];
  1590. NSMutableArray *sortedInsertMutableItems = [[_insertItems sortedArrayUsingSelector:@selector(compareIndexPaths:)] mutableCopy];
  1591. for (PSTCollectionViewUpdateItem *deleteItem in sortedDeletedMutableItems) {
  1592. if ([deleteItem isSectionOperation]) {
  1593. NSAssert(deleteItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
  1594. @"attempt to delete section (%ld) that doesn't exist (there are only %ld sections before update)",
  1595. (long)deleteItem.indexPathBeforeUpdate.section,
  1596. (long)[oldCollectionViewData numberOfSections]);
  1597. for (PSTCollectionViewUpdateItem *moveItem in sortedMutableMoveItems) {
  1598. if (moveItem.indexPathBeforeUpdate.section == deleteItem.indexPathBeforeUpdate.section) {
  1599. if (moveItem.isSectionOperation)
  1600. NSAssert(NO, @"attempt to delete and move from the same section %ld", (long)deleteItem.indexPathBeforeUpdate.section);
  1601. else
  1602. NSAssert(NO, @"attempt to delete and move from the same section (%@)", moveItem.indexPathBeforeUpdate);
  1603. }
  1604. }
  1605. }else {
  1606. NSAssert(deleteItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
  1607. @"attempt to delete item (%@) that doesn't exist (there are only %ld sections before update)",
  1608. deleteItem.indexPathBeforeUpdate,
  1609. (long)[oldCollectionViewData numberOfSections]);
  1610. NSAssert(deleteItem.indexPathBeforeUpdate.item < [oldCollectionViewData numberOfItemsInSection:deleteItem.indexPathBeforeUpdate.section],
  1611. @"attempt to delete item (%@) that doesn't exist (there are only %ld items in section%ld before update)",
  1612. deleteItem.indexPathBeforeUpdate,
  1613. (long)[oldCollectionViewData numberOfItemsInSection:deleteItem.indexPathBeforeUpdate.section],
  1614. (long)deleteItem.indexPathBeforeUpdate.section);
  1615. for (PSTCollectionViewUpdateItem *moveItem in sortedMutableMoveItems) {
  1616. NSAssert(![deleteItem.indexPathBeforeUpdate isEqual:moveItem.indexPathBeforeUpdate],
  1617. @"attempt to delete and move the same item (%@)", deleteItem.indexPathBeforeUpdate);
  1618. }
  1619. if (!operations[@(deleteItem.indexPathBeforeUpdate.section)])
  1620. operations[@(deleteItem.indexPathBeforeUpdate.section)] = [NSMutableDictionary dictionary];
  1621. operations[@(deleteItem.indexPathBeforeUpdate.section)][@"deleted"] =
  1622. @([operations[@(deleteItem.indexPathBeforeUpdate.section)][@"deleted"] intValue] + 1);
  1623. }
  1624. }
  1625. for (NSUInteger i = 0; i < sortedInsertMutableItems.count; i++) {
  1626. PSTCollectionViewUpdateItem *insertItem = sortedInsertMutableItems[i];
  1627. NSIndexPath *indexPath = insertItem.indexPathAfterUpdate;
  1628. BOOL sectionOperation = [insertItem isSectionOperation];
  1629. if (sectionOperation) {
  1630. NSAssert([indexPath section] < [_collectionViewData numberOfSections],
  1631. @"attempt to insert %ld but there are only %ld sections after update",
  1632. (long)[indexPath section], (long)[_collectionViewData numberOfSections]);
  1633. for (PSTCollectionViewUpdateItem *moveItem in sortedMutableMoveItems) {
  1634. if ([moveItem.indexPathAfterUpdate isEqual:indexPath]) {
  1635. if (moveItem.isSectionOperation)
  1636. NSAssert(NO, @"attempt to perform an insert and a move to the same section (%ld)", (long)indexPath.section);
  1637. }
  1638. }
  1639. NSUInteger j = i + 1;
  1640. while (j < sortedInsertMutableItems.count) {
  1641. PSTCollectionViewUpdateItem *nextInsertItem = sortedInsertMutableItems[j];
  1642. if (nextInsertItem.indexPathAfterUpdate.section == indexPath.section) {
  1643. NSAssert(nextInsertItem.indexPathAfterUpdate.item < [_collectionViewData numberOfItemsInSection:indexPath.section],
  1644. @"attempt to insert item %ld into section %ld, but there are only %ld items in section %ld after the update",
  1645. (long)nextInsertItem.indexPathAfterUpdate.item,
  1646. (long)indexPath.section,
  1647. (long)[_collectionViewData numberOfItemsInSection:indexPath.section],
  1648. (long)indexPath.section);
  1649. [sortedInsertMutableItems removeObjectAtIndex:j];
  1650. }
  1651. else break;
  1652. }
  1653. }else {
  1654. NSAssert(indexPath.item < [_collectionViewData numberOfItemsInSection:indexPath.section],
  1655. @"attempt to insert item to (%@) but there are only %ld items in section %ld after update",
  1656. indexPath,
  1657. (long)[_collectionViewData numberOfItemsInSection:indexPath.section],
  1658. (long)indexPath.section);
  1659. if (!operations[@(indexPath.section)])
  1660. operations[@(indexPath.section)] = [NSMutableDictionary dictionary];
  1661. operations[@(indexPath.section)][@"inserted"] =
  1662. @([operations[@(indexPath.section)][@"inserted"] intValue] + 1);
  1663. }
  1664. }
  1665. for (PSTCollectionViewUpdateItem *sortedItem in sortedMutableMoveItems) {
  1666. if (sortedItem.isSectionOperation) {
  1667. NSAssert(sortedItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
  1668. @"attempt to move section (%ld) that doesn't exist (%ld sections before update)",
  1669. (long)sortedItem.indexPathBeforeUpdate.section,
  1670. (long)[oldCollectionViewData numberOfSections]);
  1671. NSAssert(sortedItem.indexPathAfterUpdate.section < [_collectionViewData numberOfSections],
  1672. @"attempt to move section to %ld but there are only %ld sections after update",
  1673. (long)sortedItem.indexPathAfterUpdate.section,
  1674. (long)[_collectionViewData numberOfSections]);
  1675. }else {
  1676. NSAssert(sortedItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
  1677. @"attempt to move item (%@) that doesn't exist (%ld sections before update)",
  1678. sortedItem, (long)[oldCollectionViewData numberOfSections]);
  1679. NSAssert(sortedItem.indexPathBeforeUpdate.item < [oldCollectionViewData numberOfItemsInSection:sortedItem.indexPathBeforeUpdate.section],
  1680. @"attempt to move item (%@) that doesn't exist (%ld items in section %ld before update)",
  1681. sortedItem,
  1682. (long)[oldCollectionViewData numberOfItemsInSection:sortedItem.indexPathBeforeUpdate.section],
  1683. (long)sortedItem.indexPathBeforeUpdate.section);
  1684. NSAssert(sortedItem.indexPathAfterUpdate.section < [_collectionViewData numberOfSections],
  1685. @"attempt to move item to (%@) but there are only %ld sections after update",
  1686. sortedItem.indexPathAfterUpdate,
  1687. (long)[_collectionViewData numberOfSections]);
  1688. NSAssert(sortedItem.indexPathAfterUpdate.item < [_collectionViewData numberOfItemsInSection:sortedItem.indexPathAfterUpdate.section],
  1689. @"attempt to move item to (%@) but there are only %ld items in section %ld after update",
  1690. sortedItem,
  1691. (long)[_collectionViewData numberOfItemsInSection:sortedItem.indexPathAfterUpdate.section],
  1692. (long)sortedItem.indexPathAfterUpdate.section);
  1693. }
  1694. if (!operations[@(sortedItem.indexPathBeforeUpdate.section)])
  1695. operations[@(sortedItem.indexPathBeforeUpdate.section)] = [NSMutableDictionary dictionary];
  1696. if (!operations[@(sortedItem.indexPathAfterUpdate.section)])
  1697. operations[@(sortedItem.indexPathAfterUpdate.section)] = [NSMutableDictionary dictionary];
  1698. operations[@(sortedItem.indexPathBeforeUpdate.section)][@"movedOut"] =
  1699. @([operations[@(sortedItem.indexPathBeforeUpdate.section)][@"movedOut"] intValue] + 1);
  1700. operations[@(sortedItem.indexPathAfterUpdate.section)][@"movedIn"] =
  1701. @([operations[@(sortedItem.indexPathAfterUpdate.section)][@"movedIn"] intValue] + 1);
  1702. }
  1703. #if !defined NS_BLOCK_ASSERTIONS
  1704. for (NSNumber *sectionKey in [operations keyEnumerator]) {
  1705. NSInteger section = [sectionKey intValue];
  1706. NSInteger insertedCount = [operations[sectionKey][@"inserted"] intValue];
  1707. NSInteger deletedCount = [operations[sectionKey][@"deleted"] intValue];
  1708. NSInteger movedInCount = [operations[sectionKey][@"movedIn"] intValue];
  1709. NSInteger movedOutCount = [operations[sectionKey][@"movedOut"] intValue];
  1710. NSAssert([oldCollectionViewData numberOfItemsInSection:section] + insertedCount - deletedCount + movedInCount - movedOutCount ==
  1711. [_collectionViewData numberOfItemsInSection:section],
  1712. @"invalid update in section %ld: number of items after update (%ld) should be equal to the number of items before update (%ld) "\
  1713. "plus count of inserted items (%ld), minus count of deleted items (%ld), plus count of items moved in (%ld), minus count of items moved out (%ld)",
  1714. (long)section,
  1715. (long)[_collectionViewData numberOfItemsInSection:section],
  1716. (long)[oldCollectionViewData numberOfItemsInSection:section],
  1717. (long)insertedCount, (long)deletedCount, (long)movedInCount, (long)movedOutCount);
  1718. }
  1719. #endif
  1720. [someMutableArr2 addObjectsFromArray:sortedDeletedMutableItems];
  1721. [someMutableArr3 addObjectsFromArray:sortedInsertMutableItems];
  1722. [someMutableArr1 addObjectsFromArray:[someMutableArr2 sortedArrayUsingSelector:@selector(inverseCompareIndexPaths:)]];
  1723. [someMutableArr1 addObjectsFromArray:sortedMutableMoveItems];
  1724. [someMutableArr1 addObjectsFromArray:[someMutableArr3 sortedArrayUsingSelector:@selector(compareIndexPaths:)]];
  1725. NSMutableArray *layoutUpdateItems = [[NSMutableArray alloc] init];
  1726. [layoutUpdateItems addObjectsFromArray:sortedDeletedMutableItems];
  1727. [layoutUpdateItems addObjectsFromArray:sortedMutableMoveItems];
  1728. [layoutUpdateItems addObjectsFromArray:sortedInsertMutableItems];
  1729. NSMutableArray *newModel = [NSMutableArray array];
  1730. for (NSInteger i = 0; i < [oldCollectionViewData numberOfSections]; i++) {
  1731. NSMutableArray *sectionArr = [NSMutableArray array];
  1732. for (NSInteger j = 0; j < [oldCollectionViewData numberOfItemsInSection:i]; j++)
  1733. [sectionArr addObject:@([oldCollectionViewData globalIndexForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]])];
  1734. [newModel addObject:sectionArr];
  1735. }
  1736. for (PSTCollectionViewUpdateItem *updateItem in layoutUpdateItems) {
  1737. switch (updateItem.updateAction) {
  1738. case PSTCollectionUpdateActionDelete: {
  1739. if (updateItem.isSectionOperation) {
  1740. // section updates are ignored anyway in animation code. If not commented, mixing rows and section deletion causes crash in else below
  1741. // [newModel removeObjectAtIndex:updateItem.indexPathBeforeUpdate.section];
  1742. }else {
  1743. [(NSMutableArray *)newModel[updateItem.indexPathBeforeUpdate.section]
  1744. removeObjectAtIndex:updateItem.indexPathBeforeUpdate.item];
  1745. }
  1746. }
  1747. break;
  1748. case PSTCollectionUpdateActionInsert: {
  1749. if (updateItem.isSectionOperation) {
  1750. [newModel insertObject:[[NSMutableArray alloc] init]
  1751. atIndex:updateItem.indexPathAfterUpdate.section];
  1752. }else {
  1753. [(NSMutableArray *)newModel[updateItem.indexPathAfterUpdate.section]
  1754. insertObject:@(NSNotFound)
  1755. atIndex:updateItem.indexPathAfterUpdate.item];
  1756. }
  1757. }
  1758. break;
  1759. case PSTCollectionUpdateActionMove: {
  1760. if (updateItem.isSectionOperation) {
  1761. id section = newModel[updateItem.indexPathBeforeUpdate.section];
  1762. [newModel insertObject:section atIndex:updateItem.indexPathAfterUpdate.section];
  1763. }
  1764. else {
  1765. id object = @([oldCollectionViewData globalIndexForItemAtIndexPath:updateItem.indexPathBeforeUpdate]);
  1766. [newModel[updateItem.indexPathBeforeUpdate.section] removeObject:object];
  1767. [newModel[updateItem.indexPathAfterUpdate.section] insertObject:object
  1768. atIndex:updateItem.indexPathAfterUpdate.item];
  1769. }
  1770. }
  1771. break;
  1772. default: break;
  1773. }
  1774. }
  1775. NSMutableArray *oldToNewMap = [NSMutableArray arrayWithCapacity:[oldCollectionViewData numberOfItems]];
  1776. NSMutableArray *newToOldMap = [NSMutableArray arrayWithCapacity:[_collectionViewData numberOfItems]];
  1777. for (NSInteger i = 0; i < [oldCollectionViewData numberOfItems]; i++)
  1778. [oldToNewMap addObject:@(NSNotFound)];
  1779. for (NSInteger i = 0; i < [_collectionViewData numberOfItems]; i++)
  1780. [newToOldMap addObject:@(NSNotFound)];
  1781. for (NSUInteger i = 0; i < newModel.count; i++) {
  1782. NSMutableArray *section = newModel[i];
  1783. for (NSUInteger j = 0; j < section.count; j++) {
  1784. NSInteger newGlobalIndex = [_collectionViewData globalIndexForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]];
  1785. if ([section[j] integerValue] != NSNotFound)
  1786. oldToNewMap[[section[j] intValue]] = @(newGlobalIndex);
  1787. if (newGlobalIndex != NSNotFound)
  1788. newToOldMap[newGlobalIndex] = section[j];
  1789. }
  1790. }
  1791. _update = @{@"oldModel" : oldCollectionViewData, @"newModel" : _collectionViewData, @"oldToNewIndexMap" : oldToNewMap, @"newToOldIndexMap" : newToOldMap};
  1792. [self updateWithItems:someMutableArr1];
  1793. _originalInsertItems = nil;
  1794. _originalDeleteItems = nil;
  1795. _insertItems = nil;
  1796. _deleteItems = nil;
  1797. _moveItems = nil;
  1798. _reloadItems = nil;
  1799. _update = nil;
  1800. _updateCount--;
  1801. _collectionViewFlags.updating = NO;
  1802. [self resumeReloads];
  1803. }
  1804. - (void)updateRowsAtIndexPaths:(NSArray *)indexPaths updateAction:(PSTCollectionUpdateAction)updateAction {
  1805. BOOL updating = _collectionViewFlags.updating;
  1806. if (!updating) [self setupCellAnimations];
  1807. NSMutableArray *array = [self arrayForUpdateAction:updateAction]; //returns appropriate empty array if not exists
  1808. for (NSIndexPath *indexPath in indexPaths) {
  1809. PSTCollectionViewUpdateItem *updateItem = [[PSTCollectionViewUpdateItem alloc] initWithAction:updateAction forIndexPath:indexPath];
  1810. [array addObject:updateItem];
  1811. }
  1812. if (!updating) [self endItemAnimations];
  1813. }
  1814. - (void)updateSections:(NSIndexSet *)sections updateAction:(PSTCollectionUpdateAction)updateAction {
  1815. BOOL updating = _collectionViewFlags.updating;
  1816. if (!updating) [self setupCellAnimations];
  1817. NSMutableArray *updateActions = [self arrayForUpdateAction:updateAction];
  1818. [sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL *stop) {
  1819. PSTCollectionViewUpdateItem *updateItem = [[PSTCollectionViewUpdateItem alloc] initWithAction:updateAction forIndexPath:[NSIndexPath indexPathForItem:NSNotFound inSection:section]];
  1820. [updateActions addObject:updateItem];
  1821. }];
  1822. if (!updating) [self endItemAnimations];
  1823. }
  1824. ///////////////////////////////////////////////////////////////////////////////////////////
  1825. #pragma mark - PSTCollection/UICollection interoperability
  1826. #ifdef kPSUIInteroperabilityEnabled
  1827. #import <objc/message.h>
  1828. - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
  1829. NSMethodSignature *sig = [super methodSignatureForSelector:selector];
  1830. if(!sig) {
  1831. NSString *selString = NSStringFromSelector(selector);
  1832. if ([selString hasPrefix:@"_"]) {
  1833. SEL cleanedSelector = NSSelectorFromString([selString substringFromIndex:1]);
  1834. sig = [super methodSignatureForSelector:cleanedSelector];
  1835. }
  1836. }
  1837. return sig;
  1838. }
  1839. - (void)forwardInvocation:(NSInvocation *)inv {
  1840. NSString *selString = NSStringFromSelector([inv selector]);
  1841. if ([selString hasPrefix:@"_"]) {
  1842. SEL cleanedSelector = NSSelectorFromString([selString substringFromIndex:1]);
  1843. if ([self respondsToSelector:cleanedSelector]) {
  1844. // dynamically add method for faster resolving
  1845. Method newMethod = class_getInstanceMethod(self.class, [inv selector]);
  1846. IMP underscoreIMP = imp_implementationWithBlock(^(id _self) {
  1847. return objc_msgSend(_self, cleanedSelector);
  1848. });
  1849. class_addMethod(self.class, [inv selector], underscoreIMP, method_getTypeEncoding(newMethod));
  1850. // invoke now
  1851. inv.selector = cleanedSelector;
  1852. [inv invokeWithTarget:self];
  1853. }
  1854. }else {
  1855. [super forwardInvocation:inv];
  1856. }
  1857. }
  1858. #endif
  1859. @end
  1860. ///////////////////////////////////////////////////////////////////////////////////////////
  1861. #pragma mark - Runtime Additions to create UICollectionView
  1862. @implementation PSUICollectionView_ @end
  1863. @implementation PSUICollectionViewCell_ @end
  1864. @implementation PSUICollectionReusableView_ @end
  1865. @implementation PSUICollectionViewLayout_ @end
  1866. @implementation PSUICollectionViewFlowLayout_ @end
  1867. @implementation PSUICollectionViewLayoutAttributes_ @end
  1868. @implementation PSUICollectionViewController_ @end
  1869. static BOOL PSTRegisterClass(NSString *UIClassName, Class PSTClass) {
  1870. NSCParameterAssert(UIClassName && PSTClass);
  1871. Class UIClass = NSClassFromString(UIClassName);
  1872. if (UIClass) {
  1873. // Class size need to be the same for class_setSuperclass to work.
  1874. // If the UIKit class is smaller then our subclass, ivars won't clash, so there's no issue.
  1875. long sizeDifference = class_getInstanceSize(UIClass) - class_getInstanceSize(PSTClass);
  1876. if (sizeDifference > 0) {
  1877. NSLog(@"Warning! ivar size mismatch in %@ - can't change the superclass.", PSTClass);
  1878. return NO;
  1879. } else {
  1880. #pragma clang diagnostic push
  1881. #pragma clang diagnostic ignored "-Wdeprecated-declarations"
  1882. // class_setSuperclass is deprecated, but still exists and works on iOS6/7.
  1883. class_setSuperclass(PSTClass, UIClass);
  1884. #pragma clang diagnostic pop
  1885. }
  1886. } else {
  1887. // We're most likely on iOS5, the requested UIKit class doesn't exist, so we create it dynamically.
  1888. if ((UIClass = objc_allocateClassPair(PSTClass, UIClassName.UTF8String, 0))) {
  1889. objc_registerClassPair(UIClass);
  1890. }
  1891. }
  1892. return YES;
  1893. }
  1894. // Create subclasses that pose as UICollectionView et al, if not available at runtime.
  1895. __attribute__((constructor)) static void PSTCreateUICollectionViewClasses(void) {
  1896. if (objc_getClass("PSTCollectionViewDisableForwardToUICollectionViewSentinel")) return;
  1897. @autoreleasepool {
  1898. // Change superclass at runtime. This allows seamless switching from PST* to UI* at runtime.
  1899. PSTRegisterClass(@"UICollectionView", PSUICollectionView_.class);
  1900. PSTRegisterClass(@"UICollectionViewCell", PSUICollectionViewCell_.class);
  1901. PSTRegisterClass(@"UICollectionReusableView", PSUICollectionReusableView_.class);
  1902. PSTRegisterClass(@"UICollectionViewLayout", PSUICollectionViewLayout_.class);
  1903. PSTRegisterClass(@"UICollectionViewFlowLayout", PSUICollectionViewFlowLayout_.class);
  1904. PSTRegisterClass(@"UICollectionViewLayoutAttributes", PSUICollectionViewLayoutAttributes_.class);
  1905. PSTRegisterClass(@"UICollectionViewController", PSUICollectionViewController_.class);
  1906. // add PSUI classes at runtime to make Interface Builder sane
  1907. // (IB doesn't allow adding the PSUICollectionView_ types but doesn't complain on unknown classes)
  1908. // The class name may already be in use. This may happen if this code is running for the second time (first for an app bundle, then again for a unit test bundle).
  1909. Class c;
  1910. if ((c = objc_allocateClassPair(PSUICollectionView_.class, "PSUICollectionView", 0))) objc_registerClassPair(c);
  1911. if ((c = objc_allocateClassPair(PSUICollectionViewCell_.class, "PSUICollectionViewCell", 0))) objc_registerClassPair(c);
  1912. if ((c = objc_allocateClassPair(PSUICollectionReusableView_.class, "PSUICollectionReusableView", 0))) objc_registerClassPair(c);
  1913. if ((c = objc_allocateClassPair(PSUICollectionViewLayout_.class, "PSUICollectionViewLayout", 0))) objc_registerClassPair(c);
  1914. if ((c = objc_allocateClassPair(PSUICollectionViewFlowLayout_.class, "PSUICollectionViewFlowLayout", 0))) objc_registerClassPair(c);
  1915. if ((c = objc_allocateClassPair(PSUICollectionViewLayoutAttributes_.class, "PSUICollectionViewLayoutAttributes", 0)))objc_registerClassPair(c);
  1916. if ((c = objc_allocateClassPair(PSUICollectionViewController_.class, "PSUICollectionViewController", 0))) objc_registerClassPair(c);
  1917. }
  1918. }
  1919. CGFloat PSTSimulatorAnimationDragCoefficient(void) {
  1920. static CGFloat (*UIAnimationDragCoefficient)(void) = NULL;
  1921. #if TARGET_IPHONE_SIMULATOR
  1922. static dispatch_once_t onceToken;
  1923. dispatch_once(&onceToken, ^{
  1924. UIAnimationDragCoefficient = (CGFloat (*)(void))dlsym(RTLD_DEFAULT, "UIAnimationDragCoefficient");
  1925. });
  1926. #endif
  1927. return UIAnimationDragCoefficient ? UIAnimationDragCoefficient() : 1.f;
  1928. }
  1929. // helper to check for ivar layout
  1930. #if 0
  1931. static void PSTPrintIvarsForClass(Class aClass) {
  1932. unsigned int varCount;
  1933. Ivar *vars = class_copyIvarList(aClass, &varCount);
  1934. for (int i = 0; i < varCount; i++) {
  1935. NSLog(@"%s %s", ivar_getTypeEncoding(vars[i]), ivar_getName(vars[i]));
  1936. }
  1937. free(vars);
  1938. }
  1939. __attribute__((constructor)) static void PSTCheckIfIVarLayoutIsEqualSize(void) {
  1940. @autoreleasepool {
  1941. NSLog(@"PSTCollectionView size = %zd, UICollectionView size = %zd", class_getInstanceSize(PSTCollectionView.class),class_getInstanceSize(UICollectionView.class));
  1942. NSLog(@"PSTCollectionViewCell size = %zd, UICollectionViewCell size = %zd", class_getInstanceSize(PSTCollectionViewCell.class),class_getInstanceSize(UICollectionViewCell.class));
  1943. NSLog(@"PSTCollectionViewController size = %zd, UICollectionViewController size = %zd", class_getInstanceSize(PSTCollectionViewController.class),class_getInstanceSize(UICollectionViewController.class));
  1944. NSLog(@"PSTCollectionViewLayout size = %zd, UICollectionViewLayout size = %zd", class_getInstanceSize(PSTCollectionViewLayout.class),class_getInstanceSize(UICollectionViewLayout.class));
  1945. NSLog(@"PSTCollectionViewFlowLayout size = %zd, UICollectionViewFlowLayout size = %zd", class_getInstanceSize(PSTCollectionViewFlowLayout.class),class_getInstanceSize(UICollectionViewFlowLayout.class));
  1946. //PSTPrintIvarsForClass(PSTCollectionViewFlowLayout.class); NSLog(@"\n\n\n");PSTPrintIvarsForClass(UICollectionViewFlowLayout.class);
  1947. }
  1948. }
  1949. #endif