PSTCollectionView.m 110 KB

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