123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310 |
- //
- // PSTCollectionView.m
- // PSPDFKit
- //
- // Copyright (c) 2012-2013 Peter Steinberger. All rights reserved.
- //
- #import "PSTCollectionView.h"
- #import "PSTCollectionViewData.h"
- #import "PSTCollectionViewLayout+Internals.h"
- #import "PSTCollectionViewItemKey.h"
- #import <objc/runtime.h>
- #if TARGET_IPHONE_SIMULATOR
- #import <dlfcn.h>
- #endif
- #import <tgmath.h>
- @interface PSTCollectionViewLayout (Internal)
- @property (nonatomic, unsafe_unretained) PSTCollectionView *collectionView;
- @end
- @interface PSTCollectionViewData (Internal)
- - (void)prepareToLoadData;
- @end
- @interface PSTCollectionViewCell (Internal)
- - (void)performSelectionSegue;
- @end
- @interface PSTCollectionViewUpdateItem ()
- - (NSIndexPath *)indexPath;
- - (BOOL)isSectionOperation;
- @end
- @interface PSTCollectionViewLayoutAttributes () {
- char junk[128];
- }
- @property (nonatomic, copy) NSString *elementKind;
- @end
- CGFloat PSTSimulatorAnimationDragCoefficient(void);
- @class PSTCollectionViewExt;
- @interface PSTCollectionView () <UIScrollViewDelegate> {
- // ivar layout needs to EQUAL to UICollectionView.
- PSTCollectionViewLayout *_layout;
- __unsafe_unretained id<PSTCollectionViewDataSource> _dataSource;
- UIView *_backgroundView;
- NSMutableSet *_indexPathsForSelectedItems;
- NSMutableDictionary *_cellReuseQueues;
- NSMutableDictionary *_supplementaryViewReuseQueues;
- NSMutableDictionary *_decorationViewReuseQueues;
- NSMutableSet *_indexPathsForHighlightedItems;
- int _reloadingSuspendedCount;
- PSTCollectionReusableView *_firstResponderView;
- UIView *_newContentView;
- int _firstResponderViewType;
- NSString *_firstResponderViewKind;
- NSIndexPath *_firstResponderIndexPath;
- NSMutableDictionary *_allVisibleViewsDict;
- NSIndexPath *_pendingSelectionIndexPath;
- NSMutableSet *_pendingDeselectionIndexPaths;
- PSTCollectionViewData *_collectionViewData;
- id _update;
- CGRect _visibleBoundRects;
- CGRect _preRotationBounds;
- CGPoint _rotationBoundsOffset;
- int _rotationAnimationCount;
- int _updateCount;
- NSMutableArray *_insertItems;
- NSMutableArray *_deleteItems;
- NSMutableArray *_reloadItems;
- NSMutableArray *_moveItems;
- NSArray *_originalInsertItems;
- NSArray *_originalDeleteItems;
- UITouch *_currentTouch;
- void (^_updateCompletionHandler)(BOOL finished);
- NSMutableDictionary *_cellClassDict;
- NSMutableDictionary *_cellNibDict;
- NSMutableDictionary *_supplementaryViewClassDict;
- NSMutableDictionary *_supplementaryViewNibDict;
- NSMutableDictionary *_cellNibExternalObjectsTables;
- NSMutableDictionary *_supplementaryViewNibExternalObjectsTables;
- struct {
- unsigned int delegateShouldHighlightItemAtIndexPath : 1;
- unsigned int delegateDidHighlightItemAtIndexPath : 1;
- unsigned int delegateDidUnhighlightItemAtIndexPath : 1;
- unsigned int delegateShouldSelectItemAtIndexPath : 1;
- unsigned int delegateShouldDeselectItemAtIndexPath : 1;
- unsigned int delegateDidSelectItemAtIndexPath : 1;
- unsigned int delegateDidDeselectItemAtIndexPath : 1;
- unsigned int delegateSupportsMenus : 1;
- unsigned int delegateDidEndDisplayingCell : 1;
- unsigned int delegateDidEndDisplayingSupplementaryView : 1;
- unsigned int dataSourceNumberOfSections : 1;
- unsigned int dataSourceViewForSupplementaryElement : 1;
- unsigned int reloadSkippedDuringSuspension : 1;
- unsigned int scheduledUpdateVisibleCells : 1;
- unsigned int scheduledUpdateVisibleCellLayoutAttributes : 1;
- unsigned int allowsSelection : 1;
- unsigned int allowsMultipleSelection : 1;
- unsigned int updating : 1;
- unsigned int fadeCellsForBoundsChange : 1;
- unsigned int updatingLayout : 1;
- unsigned int needsReload : 1;
- unsigned int reloading : 1;
- unsigned int skipLayoutDuringSnapshotting : 1;
- unsigned int layoutInvalidatedSinceLastCellUpdate : 1;
- unsigned int doneFirstLayout : 1;
- }_collectionViewFlags;
- CGPoint _lastLayoutOffset;
- char filler[232]; // [HACK] Our class needs to be larger than Apple's class for the superclass change to work.
- }
- @property (nonatomic, strong) PSTCollectionViewData *collectionViewData;
- @property (nonatomic, strong, readonly) PSTCollectionViewExt *extVars;
- @property (nonatomic, readonly) id currentUpdate;
- @property (nonatomic, readonly) NSDictionary *visibleViewsDict;
- @property (nonatomic, assign) CGRect visibleBoundRects;
- @end
- // Used by PSTCollectionView for external variables.
- // (We need to keep the total class size equal to the UICollectionView variant)
- @interface PSTCollectionViewExt : NSObject
- @property (nonatomic, unsafe_unretained) id<PSTCollectionViewDelegate> collectionViewDelegate;
- @property (nonatomic, strong) PSTCollectionViewLayout *nibLayout;
- @property (nonatomic, strong) NSDictionary *nibCellsExternalObjects;
- @property (nonatomic, strong) NSDictionary *supplementaryViewsExternalObjects;
- @property (nonatomic, strong) NSIndexPath *touchingIndexPath;
- @property (nonatomic, strong) NSIndexPath *currentIndexPath;
- @end
- @implementation PSTCollectionViewExt
- @end
- const char kPSTColletionViewExt;
- @implementation PSTCollectionView
- @synthesize collectionViewLayout = _layout;
- @synthesize currentUpdate = _update;
- @synthesize visibleViewsDict = _allVisibleViewsDict;
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - NSObject
- static void PSTCollectionViewCommonSetup(PSTCollectionView *_self) {
- _self.allowsSelection = YES;
- _self->_indexPathsForSelectedItems = [NSMutableSet new];
- _self->_indexPathsForHighlightedItems = [NSMutableSet new];
- _self->_cellReuseQueues = [NSMutableDictionary new];
- _self->_supplementaryViewReuseQueues = [NSMutableDictionary new];
- _self->_decorationViewReuseQueues = [NSMutableDictionary new];
- _self->_allVisibleViewsDict = [NSMutableDictionary new];
- _self->_cellClassDict = [NSMutableDictionary new];
- _self->_cellNibDict = [NSMutableDictionary new];
- _self->_supplementaryViewClassDict = [NSMutableDictionary new];
- _self->_supplementaryViewNibDict = [NSMutableDictionary new];
- // add class that saves additional ivars
- objc_setAssociatedObject(_self, &kPSTColletionViewExt, [PSTCollectionViewExt new], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- }
- - (id)initWithFrame:(CGRect)frame {
- return [self initWithFrame:frame collectionViewLayout:nil];
- }
- - (id)initWithFrame:(CGRect)frame collectionViewLayout:(PSTCollectionViewLayout *)layout {
- if ((self = [super initWithFrame:frame])) {
- // Set self as the UIScrollView's delegate
- [super setDelegate:self];
- PSTCollectionViewCommonSetup(self);
- self.collectionViewLayout = layout;
- _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
- }
- return self;
- }
- - (id)initWithCoder:(NSCoder *)inCoder {
- if ((self = [super initWithCoder:inCoder])) {
- // Set self as the UIScrollView's delegate
- [super setDelegate:self];
- PSTCollectionViewCommonSetup(self);
- self.extVars.nibLayout = [inCoder decodeObjectForKey:@"UICollectionLayout"];
- NSDictionary *cellExternalObjects = [inCoder decodeObjectForKey:@"UICollectionViewCellPrototypeNibExternalObjects"];
- NSDictionary *cellNibs = [inCoder decodeObjectForKey:@"UICollectionViewCellNibDict"];
- for (NSString *identifier in cellNibs.allKeys) {
- _cellNibDict[identifier] = cellNibs[identifier];
- }
- self.extVars.nibCellsExternalObjects = cellExternalObjects;
- NSDictionary *supplementaryViewExternalObjects = [inCoder decodeObjectForKey:@"UICollectionViewSupplementaryViewPrototypeNibExternalObjects"];
- NSDictionary *supplementaryViewNibs = [inCoder decodeObjectForKey:@"UICollectionViewSupplementaryViewNibDict"];
- for (NSString *identifier in supplementaryViewNibs.allKeys) {
- _supplementaryViewNibDict[identifier] = supplementaryViewNibs[identifier];
- }
- self.extVars.supplementaryViewsExternalObjects = supplementaryViewExternalObjects;
- }
- return self;
- }
- - (void)awakeFromNib {
- [super awakeFromNib];
- PSTCollectionViewLayout *nibLayout = self.extVars.nibLayout;
- if (nibLayout) {
- self.collectionViewLayout = nibLayout;
- self.extVars.nibLayout = nil;
- }
- }
- - (NSString *)description {
- return [NSString stringWithFormat:@"%@ collection view layout: %@", [super description], self.collectionViewLayout];
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - UIView
- - (void)layoutSubviews {
- [super layoutSubviews];
- // Adding alpha animation to make the relayouting smooth
- if (_collectionViewFlags.fadeCellsForBoundsChange) {
- CATransition *transition = [CATransition animation];
- transition.duration = 0.25f * PSTSimulatorAnimationDragCoefficient();
- transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
- transition.type = kCATransitionFade;
- [self.layer addAnimation:transition forKey:@"rotationAnimation"];
- }
- [_collectionViewData validateLayoutInRect:self.bounds];
- // update cells
- if (_collectionViewFlags.fadeCellsForBoundsChange) {
- [CATransaction begin];
- [CATransaction setDisableActions:YES];
- }
- if (!_collectionViewFlags.updatingLayout)
- [self updateVisibleCellsNow:YES];
- if (_collectionViewFlags.fadeCellsForBoundsChange) {
- [CATransaction commit];
- }
- // do we need to update contentSize?
- CGSize contentSize = [_collectionViewData collectionViewContentRect].size;
- if (!CGSizeEqualToSize(self.contentSize, contentSize)) {
- self.contentSize = contentSize;
- // if contentSize is different, we need to re-evaluate layout, bounds (contentOffset) might changed
- [_collectionViewData validateLayoutInRect:self.bounds];
- [self updateVisibleCellsNow:YES];
- }
- if (_backgroundView) {
- _backgroundView.frame = (CGRect){.origin=self.contentOffset, .size=self.bounds.size};
- }
- _collectionViewFlags.fadeCellsForBoundsChange = NO;
- _collectionViewFlags.doneFirstLayout = YES;
- }
- - (void)setFrame:(CGRect)frame {
- if (!CGRectEqualToRect(frame, self.frame)) {
- CGRect bounds = (CGRect){.origin=self.contentOffset, .size=frame.size};
- BOOL shouldInvalidate = [self.collectionViewLayout shouldInvalidateLayoutForBoundsChange:bounds];
- [super setFrame:frame];
- if (shouldInvalidate) {
- [self invalidateLayout];
- _collectionViewFlags.fadeCellsForBoundsChange = YES;
- }
- }
- }
- - (void)setBounds:(CGRect)bounds {
- if (!CGRectEqualToRect(bounds, self.bounds)) {
- BOOL shouldInvalidate = [self.collectionViewLayout shouldInvalidateLayoutForBoundsChange:bounds];
- [super setBounds:bounds];
- if (shouldInvalidate) {
- [self invalidateLayout];
- _collectionViewFlags.fadeCellsForBoundsChange = YES;
- }
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - UIScrollViewDelegate
- - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidScroll:)]) {
- [delegate scrollViewDidScroll:scrollView];
- }
- }
- - (void)scrollViewDidZoom:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidZoom:)]) {
- [delegate scrollViewDidZoom:scrollView];
- }
- }
- - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginDragging:)]) {
- [delegate scrollViewWillBeginDragging:scrollView];
- }
- }
- - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
- // Let collectionViewLayout decide where to stop.
- *targetContentOffset = [[self collectionViewLayout] targetContentOffsetForProposedContentOffset:*targetContentOffset withScrollingVelocity:velocity];
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillEndDragging:withVelocity:targetContentOffset:)]) {
- //if collectionViewDelegate implements this method, it may modify targetContentOffset as well
- [delegate scrollViewWillEndDragging:scrollView withVelocity:velocity targetContentOffset:targetContentOffset];
- }
- }
- - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndDragging:willDecelerate:)]) {
- [delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
- }
- // if we are in the middle of a cell touch event, perform the "touchEnded" simulation
- if (self.extVars.touchingIndexPath) {
- [self cellTouchCancelled];
- }
- }
- - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginDecelerating:)]) {
- [delegate scrollViewWillBeginDecelerating:scrollView];
- }
- }
- - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndDecelerating:)]) {
- [delegate scrollViewDidEndDecelerating:scrollView];
- }
- }
- - (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndScrollingAnimation:)]) {
- [delegate scrollViewDidEndScrollingAnimation:scrollView];
- }
- }
- - (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(viewForZoomingInScrollView:)]) {
- return [delegate viewForZoomingInScrollView:scrollView];
- }
- return nil;
- }
- - (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewWillBeginZooming:withView:)]) {
- [delegate scrollViewWillBeginZooming:scrollView withView:view];
- }
- }
- - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidEndZooming:withView:atScale:)]) {
- [delegate scrollViewDidEndZooming:scrollView withView:view atScale:scale];
- }
- }
- - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewShouldScrollToTop:)]) {
- return [delegate scrollViewShouldScrollToTop:scrollView];
- }
- return YES;
- }
- - (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
- id<PSTCollectionViewDelegate> delegate = self.extVars.collectionViewDelegate;
- if ((id)delegate != self && [delegate respondsToSelector:@selector(scrollViewDidScrollToTop:)]) {
- [delegate scrollViewDidScrollToTop:scrollView];
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Public
- - (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier {
- NSParameterAssert(cellClass);
- NSParameterAssert(identifier);
- _cellClassDict[identifier] = cellClass;
- }
- - (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier {
- NSParameterAssert(viewClass);
- NSParameterAssert(elementKind);
- NSParameterAssert(identifier);
- NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", elementKind, identifier];
- _supplementaryViewClassDict[kindAndIdentifier] = viewClass;
- }
- - (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier {
- NSArray *topLevelObjects = [nib instantiateWithOwner:nil options:nil];
- #pragma unused(topLevelObjects)
- NSAssert(topLevelObjects.count == 1 && [topLevelObjects[0] isKindOfClass:PSTCollectionViewCell.class], @"must contain exactly 1 top level object which is a PSTCollectionViewCell");
- _cellNibDict[identifier] = nib;
- }
- - (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier {
- NSArray *topLevelObjects = [nib instantiateWithOwner:nil options:nil];
- #pragma unused(topLevelObjects)
- NSAssert(topLevelObjects.count == 1 && [topLevelObjects[0] isKindOfClass:PSTCollectionReusableView.class], @"must contain exactly 1 top level object which is a PSTCollectionReusableView");
- NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", kind, identifier];
- _supplementaryViewNibDict[kindAndIdentifier] = nib;
- }
- - (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
- // de-queue cell (if available)
- NSMutableArray *reusableCells = _cellReuseQueues[identifier];
- PSTCollectionViewCell *cell = [reusableCells lastObject];
- PSTCollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
- if (cell) {
- [reusableCells removeObjectAtIndex:reusableCells.count - 1];
- }else {
- if (_cellNibDict[identifier]) {
- // Cell was registered via registerNib:forCellWithReuseIdentifier:
- UINib *cellNib = _cellNibDict[identifier];
- NSDictionary *externalObjects = self.extVars.nibCellsExternalObjects[identifier];
- if (externalObjects) {
- cell = [cellNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
- }else {
- cell = [cellNib instantiateWithOwner:self options:nil][0];
- }
- }else {
- Class cellClass = _cellClassDict[identifier];
- // compatibility layer
- Class collectionViewCellClass = NSClassFromString(@"UICollectionViewCell");
- if (collectionViewCellClass && [cellClass isEqual:collectionViewCellClass]) {
- cellClass = PSTCollectionViewCell.class;
- }
- if (cellClass == nil) {
- @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for identifier %@", identifier] userInfo:nil];
- }
- if (attributes) {
- cell = [[cellClass alloc] initWithFrame:attributes.frame];
- }else {
- cell = [cellClass new];
- }
- }
- cell.collectionView = self;
- cell.reuseIdentifier = identifier;
- }
- [cell applyLayoutAttributes:attributes];
- return cell;
- }
- - (id)dequeueReusableSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
- NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", elementKind, identifier];
- NSMutableArray *reusableViews = _supplementaryViewReuseQueues[kindAndIdentifier];
- PSTCollectionReusableView *view = [reusableViews lastObject];
- if (view) {
- [reusableViews removeObjectAtIndex:reusableViews.count - 1];
- }else {
- if (_supplementaryViewNibDict[kindAndIdentifier]) {
- // supplementary view was registered via registerNib:forCellWithReuseIdentifier:
- UINib *supplementaryViewNib = _supplementaryViewNibDict[kindAndIdentifier];
- NSDictionary *externalObjects = self.extVars.supplementaryViewsExternalObjects[kindAndIdentifier];
- if (externalObjects) {
- view = [supplementaryViewNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
- }else {
- view = [supplementaryViewNib instantiateWithOwner:self options:nil][0];
- }
- }else {
- Class viewClass = _supplementaryViewClassDict[kindAndIdentifier];
- Class reusableViewClass = NSClassFromString(@"UICollectionReusableView");
- if (reusableViewClass && [viewClass isEqual:reusableViewClass]) {
- viewClass = PSTCollectionReusableView.class;
- }
- if (viewClass == nil) {
- @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for kind/identifier %@", kindAndIdentifier] userInfo:nil];
- }
- if (self.collectionViewLayout) {
- PSTCollectionViewLayoutAttributes *attributes = [self.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:elementKind atIndexPath:indexPath];
- if (attributes) {
- view = [[viewClass alloc] initWithFrame:attributes.frame];
- }
- }else {
- view = [viewClass new];
- }
- }
- view.collectionView = self;
- view.reuseIdentifier = identifier;
- }
- return view;
- }
- - (id)dequeueReusableOrCreateDecorationViewOfKind:(NSString *)elementKind forIndexPath:(NSIndexPath *)indexPath {
- NSMutableArray *reusableViews = _decorationViewReuseQueues[elementKind];
- PSTCollectionReusableView *view = [reusableViews lastObject];
- PSTCollectionViewLayout *collectionViewLayout = self.collectionViewLayout;
- PSTCollectionViewLayoutAttributes *attributes = [collectionViewLayout layoutAttributesForDecorationViewOfKind:elementKind atIndexPath:indexPath];
- if (view) {
- [reusableViews removeObjectAtIndex:reusableViews.count - 1];
- }else {
- NSDictionary *decorationViewNibDict = collectionViewLayout.decorationViewNibDict;
- NSDictionary *decorationViewExternalObjects = collectionViewLayout.decorationViewExternalObjectsTables;
- if (decorationViewNibDict[elementKind]) {
- // supplementary view was registered via registerNib:forCellWithReuseIdentifier:
- UINib *supplementaryViewNib = decorationViewNibDict[elementKind];
- NSDictionary *externalObjects = decorationViewExternalObjects[elementKind];
- if (externalObjects) {
- view = [supplementaryViewNib instantiateWithOwner:self options:@{UINibExternalObjects : externalObjects}][0];
- }else {
- view = [supplementaryViewNib instantiateWithOwner:self options:nil][0];
- }
- }else {
- NSDictionary *decorationViewClassDict = collectionViewLayout.decorationViewClassDict;
- Class viewClass = decorationViewClassDict[elementKind];
- Class reusableViewClass = NSClassFromString(@"UICollectionReusableView");
- if (reusableViewClass && [viewClass isEqual:reusableViewClass]) {
- viewClass = PSTCollectionReusableView.class;
- }
- if (viewClass == nil) {
- @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"Class not registered for identifier %@", elementKind] userInfo:nil];
- }
- if (attributes) {
- view = [[viewClass alloc] initWithFrame:attributes.frame];
- }else {
- view = [viewClass new];
- }
- }
- view.collectionView = self;
- view.reuseIdentifier = elementKind;
- }
- [view applyLayoutAttributes:attributes];
- return view;
- }
- - (NSArray *)allCells {
- return [[_allVisibleViewsDict allValues] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
- return [evaluatedObject isKindOfClass:PSTCollectionViewCell.class];
- }]];
- }
- - (NSArray *)visibleCells {
- return [[_allVisibleViewsDict allValues] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
- return [evaluatedObject isKindOfClass:PSTCollectionViewCell.class] && CGRectIntersectsRect(self.bounds, [evaluatedObject frame]);
- }]];
- }
- - (void)reloadData {
- if (_reloadingSuspendedCount != 0) return;
- [self invalidateLayout];
- [_allVisibleViewsDict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
- if ([obj isKindOfClass:UIView.class]) {
- [obj removeFromSuperview];
- }
- }];
- [_allVisibleViewsDict removeAllObjects];
- for (NSIndexPath *indexPath in _indexPathsForSelectedItems) {
- PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
- selectedCell.selected = NO;
- selectedCell.highlighted = NO;
- }
- [_indexPathsForSelectedItems removeAllObjects];
- [_indexPathsForHighlightedItems removeAllObjects];
- [self setNeedsLayout];
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Query Grid
- - (NSInteger)numberOfSections {
- return [_collectionViewData numberOfSections];
- }
- - (NSInteger)numberOfItemsInSection:(NSInteger)section {
- return [_collectionViewData numberOfItemsInSection:section];
- }
- - (PSTCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
- return [[self collectionViewLayout] layoutAttributesForItemAtIndexPath:indexPath];
- }
- - (PSTCollectionViewLayoutAttributes *)layoutAttributesForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
- return [[self collectionViewLayout] layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath];
- }
- - (NSIndexPath *)indexPathForItemAtPoint:(CGPoint)point {
- PSTCollectionViewLayoutAttributes *attributes = [[self.collectionViewLayout layoutAttributesForElementsInRect:CGRectMake(point.x, point.y, 1, 1)] lastObject];
- return attributes.indexPath;
- }
- - (NSIndexPath *)indexPathForCell:(PSTCollectionViewCell *)cell {
- __block NSIndexPath *indexPath = nil;
- [_allVisibleViewsDict enumerateKeysAndObjectsWithOptions:kNilOptions usingBlock:^(id key, id obj, BOOL *stop) {
- PSTCollectionViewItemKey *itemKey = (PSTCollectionViewItemKey *)key;
- if (itemKey.type == PSTCollectionViewItemTypeCell) {
- PSTCollectionViewCell *currentCell = (PSTCollectionViewCell *)obj;
- if (currentCell == cell) {
- indexPath = itemKey.indexPath;
- *stop = YES;
- }
- }
- }];
- return indexPath;
- }
- - (PSTCollectionViewCell *)cellForItemAtIndexPath:(NSIndexPath *)indexPath {
- // NSInteger index = [_collectionViewData globalIndexForItemAtIndexPath:indexPath];
- // TODO Apple uses some kind of globalIndex for this.
- __block PSTCollectionViewCell *cell = nil;
- [_allVisibleViewsDict enumerateKeysAndObjectsWithOptions:0 usingBlock:^(id key, id obj, BOOL *stop) {
- PSTCollectionViewItemKey *itemKey = (PSTCollectionViewItemKey *)key;
- if (itemKey.type == PSTCollectionViewItemTypeCell) {
- if ([itemKey.indexPath isEqual:indexPath]) {
- cell = obj;
- *stop = YES;
- }
- }
- }];
- return cell;
- }
- - (NSArray *)indexPathsForVisibleItems {
- NSArray *visibleCells = self.visibleCells;
- NSMutableArray *indexPaths = [NSMutableArray arrayWithCapacity:visibleCells.count];
- [visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
- PSTCollectionViewCell *cell = (PSTCollectionViewCell *)obj;
- [indexPaths addObject:cell.layoutAttributes.indexPath];
- }];
- return indexPaths;
- }
- // returns nil or an array of selected index paths
- - (NSArray *)indexPathsForSelectedItems {
- return [_indexPathsForSelectedItems allObjects];
- }
- // Interacting with the collection view.
- - (void)scrollToItemAtIndexPath:(NSIndexPath *)indexPath atScrollPosition:(PSTCollectionViewScrollPosition)scrollPosition animated:(BOOL)animated {
- // Ensure grid is laid out; else we can't scroll.
- [self layoutSubviews];
- PSTCollectionViewLayoutAttributes *layoutAttributes = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath];
- if (layoutAttributes) {
- CGRect targetRect = [self makeRect:layoutAttributes.frame toScrollPosition:scrollPosition];
- [self scrollRectToVisible:targetRect animated:animated];
- }
- }
- - (CGRect)makeRect:(CGRect)targetRect toScrollPosition:(PSTCollectionViewScrollPosition)scrollPosition {
- // split parameters
- NSUInteger verticalPosition = scrollPosition&0x07; // 0000 0111
- NSUInteger horizontalPosition = scrollPosition&0x38; // 0011 1000
- if (verticalPosition != PSTCollectionViewScrollPositionNone
- && verticalPosition != PSTCollectionViewScrollPositionTop
- && verticalPosition != PSTCollectionViewScrollPositionCenteredVertically
- && verticalPosition != PSTCollectionViewScrollPositionBottom) {
- @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"PSTCollectionViewScrollPosition: attempt to use a scroll position with multiple vertical positioning styles" userInfo:nil];
- }
- if (horizontalPosition != PSTCollectionViewScrollPositionNone
- && horizontalPosition != PSTCollectionViewScrollPositionLeft
- && horizontalPosition != PSTCollectionViewScrollPositionCenteredHorizontally
- && horizontalPosition != PSTCollectionViewScrollPositionRight) {
- @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"PSTCollectionViewScrollPosition: attempt to use a scroll position with multiple horizontal positioning styles" userInfo:nil];
- }
- CGRect frame = self.layer.bounds;
- CGFloat calculateX;
- CGFloat calculateY;
- switch (verticalPosition) {
- case PSTCollectionViewScrollPositionCenteredVertically:
- calculateY = fmax(targetRect.origin.y - ((frame.size.height / 2) - (targetRect.size.height / 2)), -self.contentInset.top);
- targetRect = CGRectMake(targetRect.origin.x, calculateY, targetRect.size.width, frame.size.height);
- break;
- case PSTCollectionViewScrollPositionTop:
- targetRect = CGRectMake(targetRect.origin.x, targetRect.origin.y, targetRect.size.width, frame.size.height);
- break;
- case PSTCollectionViewScrollPositionBottom:
- calculateY = fmax(targetRect.origin.y - (frame.size.height - targetRect.size.height), -self.contentInset.top);
- targetRect = CGRectMake(targetRect.origin.x, calculateY, targetRect.size.width, frame.size.height);
- break;
- }
- switch (horizontalPosition) {
- case PSTCollectionViewScrollPositionCenteredHorizontally:
- calculateX = targetRect.origin.x - ((frame.size.width / 2) - (targetRect.size.width / 2));
- targetRect = CGRectMake(calculateX, targetRect.origin.y, frame.size.width, targetRect.size.height);
- break;
- case PSTCollectionViewScrollPositionLeft:
- targetRect = CGRectMake(targetRect.origin.x, targetRect.origin.y, frame.size.width, targetRect.size.height);
- break;
- case PSTCollectionViewScrollPositionRight:
- calculateX = targetRect.origin.x - (frame.size.width - targetRect.size.width);
- targetRect = CGRectMake(calculateX, targetRect.origin.y, frame.size.width, targetRect.size.height);
- break;
- }
- return targetRect;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Touch Handling
- - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
- [super touchesBegan:touches withEvent:event];
- // reset touching state vars
- self.extVars.touchingIndexPath = nil;
- self.extVars.currentIndexPath = nil;
- CGPoint touchPoint = [[touches anyObject] locationInView:self];
- NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
- if (indexPath && self.allowsSelection) {
- if (![self highlightItemAtIndexPath:indexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES])
- return;
- self.extVars.touchingIndexPath = indexPath;
- self.extVars.currentIndexPath = indexPath;
- if (!self.allowsMultipleSelection) {
- // temporally unhighlight background on touchesBegan (keeps selected by _indexPathsForSelectedItems)
- // single-select only mode only though
- NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
- if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:self.extVars.touchingIndexPath]) {
- // iOS6 UICollectionView deselects cell without notification
- PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:tempDeselectIndexPath];
- selectedCell.selected = NO;
- }
- }
- }
- }
- - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
- [super touchesMoved:touches withEvent:event];
- // allows moving between highlight and unhighlight state only if setHighlighted is not overwritten
- if (self.extVars.touchingIndexPath) {
- CGPoint touchPoint = [[touches anyObject] locationInView:self];
- NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
- // moving out of bounds
- if ([self.extVars.currentIndexPath isEqual:self.extVars.touchingIndexPath] &&
- ![indexPath isEqual:self.extVars.touchingIndexPath] &&
- [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES shouldCheckHighlight:YES]) {
- self.extVars.currentIndexPath = indexPath;
- // moving back into the original touching cell
- }else if (![self.extVars.currentIndexPath isEqual:self.extVars.touchingIndexPath] &&
- [indexPath isEqual:self.extVars.touchingIndexPath]) {
- [self highlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES];
- self.extVars.currentIndexPath = self.extVars.touchingIndexPath;
- }
- }
- }
- - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
- [super touchesEnded:touches withEvent:event];
- if (self.extVars.touchingIndexPath) {
- // first unhighlight the touch operation
- [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES];
- CGPoint touchPoint = [[touches anyObject] locationInView:self];
- NSIndexPath *indexPath = [self indexPathForItemAtPoint:touchPoint];
- if ([indexPath isEqual:self.extVars.touchingIndexPath]) {
- [self userSelectedItemAtIndexPath:indexPath];
- }
- else if (!self.allowsMultipleSelection) {
- NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
- if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:self.extVars.touchingIndexPath]) {
- [self cellTouchCancelled];
- }
- }
- // for pedantic reasons only - always set to nil on touchesBegan
- self.extVars.touchingIndexPath = nil;
- self.extVars.currentIndexPath = nil;
- }
- }
- - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
- [super touchesCancelled:touches withEvent:event];
- // do not mark touchingIndexPath as nil because whoever cancelled this touch will need to signal a touch up event later
- if (self.extVars.touchingIndexPath) {
- // first unhighlight the touch operation
- [self unhighlightItemAtIndexPath:self.extVars.touchingIndexPath animated:YES notifyDelegate:YES];
- }
- }
- - (void)cellTouchCancelled {
- // turn on ALL the *should be selected* cells (iOS6 UICollectionView does no state keeping or other fancy optimizations)
- // there should be no notifications as this is a silent "turn everything back on"
- for (NSIndexPath *tempDeselectedIndexPath in [_indexPathsForSelectedItems copy]) {
- PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:tempDeselectedIndexPath];
- selectedCell.selected = YES;
- }
- }
- - (void)userSelectedItemAtIndexPath:(NSIndexPath *)indexPath {
- if (self.allowsMultipleSelection && [_indexPathsForSelectedItems containsObject:indexPath]) {
- [self deselectItemAtIndexPath:indexPath animated:YES notifyDelegate:YES];
- }
- else if (self.allowsSelection) {
- [self selectItemAtIndexPath:indexPath animated:YES scrollPosition:PSTCollectionViewScrollPositionNone notifyDelegate:YES];
- }
- }
- // select item, notify delegate (internal)
- - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate {
- if (self.allowsMultipleSelection && [_indexPathsForSelectedItems containsObject:indexPath]) {
- BOOL shouldDeselect = YES;
- if (notifyDelegate && _collectionViewFlags.delegateShouldDeselectItemAtIndexPath) {
- shouldDeselect = [self.delegate collectionView:self shouldDeselectItemAtIndexPath:indexPath];
- }
- if (shouldDeselect) {
- [self deselectItemAtIndexPath:indexPath animated:animated notifyDelegate:notifyDelegate];
- }
- }
- else {
- // either single selection, or wasn't already selected in multiple selection mode
- BOOL shouldSelect = YES;
- if (notifyDelegate && _collectionViewFlags.delegateShouldSelectItemAtIndexPath) {
- shouldSelect = [self.delegate collectionView:self shouldSelectItemAtIndexPath:indexPath];
- }
- if (!self.allowsMultipleSelection) {
- // now unselect the previously selected cell for single selection
- NSIndexPath *tempDeselectIndexPath = _indexPathsForSelectedItems.anyObject;
- if (tempDeselectIndexPath && ![tempDeselectIndexPath isEqual:indexPath]) {
- [self deselectItemAtIndexPath:tempDeselectIndexPath animated:YES notifyDelegate:YES];
- }
- }
- if (shouldSelect) {
- PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
- selectedCell.selected = YES;
- [_indexPathsForSelectedItems addObject:indexPath];
- [selectedCell performSelectionSegue];
- if (scrollPosition != PSTCollectionViewScrollPositionNone) {
- [self scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
- }
- if (notifyDelegate && _collectionViewFlags.delegateDidSelectItemAtIndexPath) {
- [self.delegate collectionView:self didSelectItemAtIndexPath:indexPath];
- }
- }
- }
- }
- - (void)selectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition {
- [self selectItemAtIndexPath:indexPath animated:animated scrollPosition:scrollPosition notifyDelegate:NO];
- }
- - (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated {
- [self deselectItemAtIndexPath:indexPath animated:animated notifyDelegate:NO];
- }
- - (void)deselectItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate {
- BOOL shouldDeselect = YES;
- // deselect only relevant during multi mode
- if (self.allowsMultipleSelection && notifyDelegate && _collectionViewFlags.delegateShouldDeselectItemAtIndexPath) {
- shouldDeselect = [self.delegate collectionView:self shouldDeselectItemAtIndexPath:indexPath];
- }
- if (shouldDeselect && [_indexPathsForSelectedItems containsObject:indexPath]) {
- PSTCollectionViewCell *selectedCell = [self cellForItemAtIndexPath:indexPath];
- if (selectedCell) {
- if (selectedCell.selected) {
- selectedCell.selected = NO;
- }
- }
- [_indexPathsForSelectedItems removeObject:indexPath];
- if (notifyDelegate && _collectionViewFlags.delegateDidDeselectItemAtIndexPath) {
- [self.delegate collectionView:self didDeselectItemAtIndexPath:indexPath];
- }
- }
- }
- - (BOOL)highlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated scrollPosition:(PSTCollectionViewScrollPosition)scrollPosition notifyDelegate:(BOOL)notifyDelegate {
- BOOL shouldHighlight = YES;
- if (notifyDelegate && _collectionViewFlags.delegateShouldHighlightItemAtIndexPath) {
- shouldHighlight = [self.delegate collectionView:self shouldHighlightItemAtIndexPath:indexPath];
- }
- if (shouldHighlight) {
- PSTCollectionViewCell *highlightedCell = [self cellForItemAtIndexPath:indexPath];
- highlightedCell.highlighted = YES;
- [_indexPathsForHighlightedItems addObject:indexPath];
- if (scrollPosition != PSTCollectionViewScrollPositionNone) {
- [self scrollToItemAtIndexPath:indexPath atScrollPosition:scrollPosition animated:animated];
- }
- if (notifyDelegate && _collectionViewFlags.delegateDidHighlightItemAtIndexPath) {
- [self.delegate collectionView:self didHighlightItemAtIndexPath:indexPath];
- }
- }
- return shouldHighlight;
- }
- - (BOOL)unhighlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate {
- return [self unhighlightItemAtIndexPath:indexPath animated:animated notifyDelegate:notifyDelegate shouldCheckHighlight:NO];
- }
- - (BOOL)unhighlightItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated notifyDelegate:(BOOL)notifyDelegate shouldCheckHighlight:(BOOL)check {
- if ([_indexPathsForHighlightedItems containsObject:indexPath]) {
- PSTCollectionViewCell *highlightedCell = [self cellForItemAtIndexPath:indexPath];
- // iOS6 does not notify any delegate if the cell was never highlighted (setHighlighted overwritten) during touchMoved
- if (check && !highlightedCell.highlighted) {
- return NO;
- }
- // if multiple selection or not unhighlighting a selected item we don't perform any op
- if (highlightedCell.highlighted && [_indexPathsForSelectedItems containsObject:indexPath]) {
- highlightedCell.highlighted = YES;
- }else {
- highlightedCell.highlighted = NO;
- }
- [_indexPathsForHighlightedItems removeObject:indexPath];
- if (notifyDelegate && _collectionViewFlags.delegateDidUnhighlightItemAtIndexPath) {
- [self.delegate collectionView:self didUnhighlightItemAtIndexPath:indexPath];
- }
- return YES;
- }
- return NO;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Update Grid
- - (void)insertSections:(NSIndexSet *)sections {
- [self updateSections:sections updateAction:PSTCollectionUpdateActionInsert];
- }
- - (void)deleteSections:(NSIndexSet *)sections {
- // First delete all items
- NSMutableArray *paths = [NSMutableArray new];
- [sections enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
- for (int i = 0; i < [self numberOfItemsInSection:(NSInteger)idx]; ++i) {
- [paths addObject:[NSIndexPath indexPathForItem:i inSection:(NSInteger)idx]];
- }
- }];
- [self deleteItemsAtIndexPaths:paths];
- // Then delete the section.
- [self updateSections:sections updateAction:PSTCollectionUpdateActionDelete];
- }
- - (void)reloadSections:(NSIndexSet *)sections {
- [self updateSections:sections updateAction:PSTCollectionUpdateActionReload];
- }
- - (void)moveSection:(NSInteger)section toSection:(NSInteger)newSection {
- NSMutableArray *moveUpdateItems = [self arrayForUpdateAction:PSTCollectionUpdateActionMove];
- [moveUpdateItems addObject:
- [[PSTCollectionViewUpdateItem alloc] initWithInitialIndexPath:[NSIndexPath indexPathForItem:NSNotFound inSection:section]
- finalIndexPath:[NSIndexPath indexPathForItem:NSNotFound inSection:newSection]
- updateAction:PSTCollectionUpdateActionMove]];
- if (!_collectionViewFlags.updating) {
- [self setupCellAnimations];
- [self endItemAnimations];
- }
- }
- - (void)insertItemsAtIndexPaths:(NSArray *)indexPaths {
- [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionInsert];
- }
- - (void)deleteItemsAtIndexPaths:(NSArray *)indexPaths {
- [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionDelete];
- }
- - (void)reloadItemsAtIndexPaths:(NSArray *)indexPaths {
- [self updateRowsAtIndexPaths:indexPaths updateAction:PSTCollectionUpdateActionReload];
- }
- - (void)moveItemAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath {
- NSMutableArray *moveUpdateItems = [self arrayForUpdateAction:PSTCollectionUpdateActionMove];
- [moveUpdateItems addObject:
- [[PSTCollectionViewUpdateItem alloc] initWithInitialIndexPath:indexPath
- finalIndexPath:newIndexPath
- updateAction:PSTCollectionUpdateActionMove]];
- if (!_collectionViewFlags.updating) {
- [self setupCellAnimations];
- [self endItemAnimations];
- }
- }
- - (void)performBatchUpdates:(void (^)(void))updates completion:(void (^)(BOOL finished))completion {
- [self setupCellAnimations];
- if (updates) updates();
- if (completion) _updateCompletionHandler = completion;
- [self endItemAnimations];
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Properties
- - (void)setBackgroundView:(UIView *)backgroundView {
- if (backgroundView != _backgroundView) {
- [_backgroundView removeFromSuperview];
- _backgroundView = backgroundView;
- backgroundView.frame = (CGRect){.origin=self.contentOffset, .size=self.bounds.size};
- backgroundView.autoresizingMask = UIViewAutoresizingFlexibleHeight|UIViewAutoresizingFlexibleWidth;
- [self addSubview:backgroundView];
- [self sendSubviewToBack:backgroundView];
- }
- }
- - (void)setCollectionViewLayout:(PSTCollectionViewLayout *)layout animated:(BOOL)animated {
- if (layout == _layout) return;
- // not sure it was it original code, but here this prevents crash
- // in case we switch layout before previous one was initially loaded
- if (CGRectIsEmpty(self.bounds) || !_collectionViewFlags.doneFirstLayout) {
- _layout.collectionView = nil;
- _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
- layout.collectionView = self;
- _layout = layout;
- // originally the use method
- // _setNeedsVisibleCellsUpdate:withLayoutAttributes:
- // here with CellsUpdate set to YES and LayoutAttributes parameter set to NO
- // inside this method probably some flags are set and finally
- // setNeedsDisplay is called
- _collectionViewFlags.scheduledUpdateVisibleCells = YES;
- _collectionViewFlags.scheduledUpdateVisibleCellLayoutAttributes = NO;
- [self setNeedsDisplay];
- }
- else {
- layout.collectionView = self;
-
- _layout.collectionView = nil;
- _layout = layout;
- _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:layout];
- [_collectionViewData prepareToLoadData];
- NSArray *previouslySelectedIndexPaths = [self indexPathsForSelectedItems];
- NSMutableSet *selectedCellKeys = [NSMutableSet setWithCapacity:previouslySelectedIndexPaths.count];
- for (NSIndexPath *indexPath in previouslySelectedIndexPaths) {
- [selectedCellKeys addObject:[PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath]];
- }
- NSArray *previouslyVisibleItemsKeys = [_allVisibleViewsDict allKeys];
- NSSet *previouslyVisibleItemsKeysSet = [NSSet setWithArray:previouslyVisibleItemsKeys];
- NSMutableSet *previouslyVisibleItemsKeysSetMutable = [NSMutableSet setWithArray:previouslyVisibleItemsKeys];
- if ([selectedCellKeys intersectsSet:selectedCellKeys]) {
- [previouslyVisibleItemsKeysSetMutable intersectSet:previouslyVisibleItemsKeysSetMutable];
- }
- [self bringSubviewToFront:_allVisibleViewsDict[[previouslyVisibleItemsKeysSetMutable anyObject]]];
- CGPoint targetOffset = self.contentOffset;
- CGPoint centerPoint = CGPointMake(self.bounds.origin.x + self.bounds.size.width / 2.f,
- self.bounds.origin.y + self.bounds.size.height / 2.f);
- NSIndexPath *centerItemIndexPath = [self indexPathForItemAtPoint:centerPoint];
- if (!centerItemIndexPath) {
- NSArray *visibleItems = [self indexPathsForVisibleItems];
- if (visibleItems.count > 0) {
- centerItemIndexPath = visibleItems[visibleItems.count / 2];
- }
- }
- if (centerItemIndexPath) {
- PSTCollectionViewLayoutAttributes *layoutAttributes = [layout layoutAttributesForItemAtIndexPath:centerItemIndexPath];
- if (layoutAttributes) {
- PSTCollectionViewScrollPosition scrollPosition = PSTCollectionViewScrollPositionCenteredVertically|PSTCollectionViewScrollPositionCenteredHorizontally;
- CGRect targetRect = [self makeRect:layoutAttributes.frame toScrollPosition:scrollPosition];
- targetOffset = CGPointMake(fmax(0.f, targetRect.origin.x), fmax(0.f, targetRect.origin.y));
- }
- }
- CGRect newlyBounds = CGRectMake(targetOffset.x, targetOffset.y, self.bounds.size.width, self.bounds.size.height);
- NSArray *newlyVisibleLayoutAttrs = [_collectionViewData layoutAttributesForElementsInRect:newlyBounds];
- NSMutableDictionary *layoutInterchangeData = [NSMutableDictionary dictionaryWithCapacity:
- newlyVisibleLayoutAttrs.count + previouslyVisibleItemsKeysSet.count];
- NSMutableSet *newlyVisibleItemsKeys = [NSMutableSet set];
- for (PSTCollectionViewLayoutAttributes *attr in newlyVisibleLayoutAttrs) {
- PSTCollectionViewItemKey *newKey = [PSTCollectionViewItemKey collectionItemKeyForLayoutAttributes:attr];
- [newlyVisibleItemsKeys addObject:newKey];
- PSTCollectionViewLayoutAttributes *prevAttr = nil;
- PSTCollectionViewLayoutAttributes *newAttr = nil;
- if (newKey.type == PSTCollectionViewItemTypeDecorationView) {
- prevAttr = [self.collectionViewLayout layoutAttributesForDecorationViewOfKind:attr.representedElementKind
- atIndexPath:newKey.indexPath];
- newAttr = [layout layoutAttributesForDecorationViewOfKind:attr.representedElementKind
- atIndexPath:newKey.indexPath];
- }
- else if (newKey.type == PSTCollectionViewItemTypeCell) {
- prevAttr = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:newKey.indexPath];
- newAttr = [layout layoutAttributesForItemAtIndexPath:newKey.indexPath];
- }
- else {
- prevAttr = [self.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:attr.representedElementKind
- atIndexPath:newKey.indexPath];
- newAttr = [layout layoutAttributesForSupplementaryViewOfKind:attr.representedElementKind
- atIndexPath:newKey.indexPath];
- }
- if (prevAttr != nil && newAttr != nil) {
- layoutInterchangeData[newKey] = @{@"previousLayoutInfos": prevAttr, @"newLayoutInfos": newAttr};
- }
- }
- for (PSTCollectionViewItemKey *key in previouslyVisibleItemsKeysSet) {
- PSTCollectionViewLayoutAttributes *prevAttr = nil;
- PSTCollectionViewLayoutAttributes *newAttr = nil;
- if (key.type == PSTCollectionViewItemTypeDecorationView) {
- PSTCollectionReusableView *decorView = _allVisibleViewsDict[key];
- prevAttr = [self.collectionViewLayout layoutAttributesForDecorationViewOfKind:decorView.reuseIdentifier
- atIndexPath:key.indexPath];
- newAttr = [layout layoutAttributesForDecorationViewOfKind:decorView.reuseIdentifier
- atIndexPath:key.indexPath];
- }
- else if (key.type == PSTCollectionViewItemTypeCell) {
- prevAttr = [self.collectionViewLayout layoutAttributesForItemAtIndexPath:key.indexPath];
- newAttr = [layout layoutAttributesForItemAtIndexPath:key.indexPath];
- }
- else if (key.type == PSTCollectionViewItemTypeSupplementaryView) {
- PSTCollectionReusableView *suuplView = _allVisibleViewsDict[key];
- prevAttr = [self.collectionViewLayout layoutAttributesForSupplementaryViewOfKind:suuplView.layoutAttributes.representedElementKind
- atIndexPath:key.indexPath];
- newAttr = [layout layoutAttributesForSupplementaryViewOfKind:suuplView.layoutAttributes.representedElementKind
- atIndexPath:key.indexPath];
- }
- NSMutableDictionary *layoutInterchangeDataValue = [NSMutableDictionary dictionary];
- if (prevAttr) layoutInterchangeDataValue[@"previousLayoutInfos"] = prevAttr;
- if (newAttr) layoutInterchangeDataValue[@"newLayoutInfos"] = newAttr;
- layoutInterchangeData[key] = layoutInterchangeDataValue;
- }
- for (PSTCollectionViewItemKey *key in [layoutInterchangeData keyEnumerator]) {
- if (key.type == PSTCollectionViewItemTypeCell) {
- PSTCollectionViewCell *cell = _allVisibleViewsDict[key];
- if (!cell) {
- cell = [self createPreparedCellForItemAtIndexPath:key.indexPath
- withLayoutAttributes:layoutInterchangeData[key][@"previousLayoutInfos"]];
- _allVisibleViewsDict[key] = cell;
- [self addControlledSubview:cell];
- }
- else [cell applyLayoutAttributes:layoutInterchangeData[key][@"previousLayoutInfos"]];
- }
- else if (key.type == PSTCollectionViewItemTypeSupplementaryView) {
- PSTCollectionReusableView *view = _allVisibleViewsDict[key];
- if (!view) {
- PSTCollectionViewLayoutAttributes *attrs = layoutInterchangeData[key][@"previousLayoutInfos"];
- view = [self createPreparedSupplementaryViewForElementOfKind:attrs.representedElementKind
- atIndexPath:attrs.indexPath
- withLayoutAttributes:attrs];
- _allVisibleViewsDict[key] = view;
- [self addControlledSubview:view];
- }
- }
- else if (key.type == PSTCollectionViewItemTypeDecorationView) {
- PSTCollectionReusableView *view = _allVisibleViewsDict[key];
- if (!view) {
- PSTCollectionViewLayoutAttributes *attrs = layoutInterchangeData[key][@"previousLayoutInfos"];
- view = [self dequeueReusableOrCreateDecorationViewOfKind:attrs.representedElementKind forIndexPath:attrs.indexPath];
- _allVisibleViewsDict[key] = view;
- [self addControlledSubview:view];
- }
- }
- };
- CGRect contentRect = [_collectionViewData collectionViewContentRect];
- void (^applyNewLayoutBlock)(void) = ^{
- NSEnumerator *keys = [layoutInterchangeData keyEnumerator];
- for (PSTCollectionViewItemKey *key in keys) {
- // TODO: This is most likely not 100% the same time as in UICollectionView. Needs to be investigated.
- PSTCollectionViewCell *cell = (PSTCollectionViewCell *)_allVisibleViewsDict[key];
- [cell willTransitionFromLayout:_layout toLayout:layout];
- [cell applyLayoutAttributes:layoutInterchangeData[key][@"newLayoutInfos"]];
- [cell didTransitionFromLayout:_layout toLayout:layout];
- }
- };
- void (^freeUnusedViews)(void) = ^{
- NSMutableSet *toRemove = [NSMutableSet set];
- for (PSTCollectionViewItemKey *key in [_allVisibleViewsDict keyEnumerator]) {
- if (![newlyVisibleItemsKeys containsObject:key]) {
- if (key.type == PSTCollectionViewItemTypeCell) {
- [self reuseCell:_allVisibleViewsDict[key]];
- [toRemove addObject:key];
- }
- else if (key.type == PSTCollectionViewItemTypeSupplementaryView) {
- [self reuseSupplementaryView:_allVisibleViewsDict[key]];
- [toRemove addObject:key];
- }
- else if (key.type == PSTCollectionViewItemTypeDecorationView) {
- [self reuseDecorationView:_allVisibleViewsDict[key]];
- [toRemove addObject:key];
- }
- }
- }
- for (id key in toRemove)
- [_allVisibleViewsDict removeObjectForKey:key];
- };
- if (animated) {
- [UIView animateWithDuration:.3 animations:^{
- _collectionViewFlags.updatingLayout = YES;
- self.contentOffset = targetOffset;
- self.contentSize = contentRect.size;
- applyNewLayoutBlock();
- } completion:^(BOOL finished) {
- freeUnusedViews();
- _collectionViewFlags.updatingLayout = NO;
- // layout subviews for updating content offset or size while updating layout
- if (!CGPointEqualToPoint(self.contentOffset, targetOffset)
- || !CGSizeEqualToSize(self.contentSize, contentRect.size)) {
- [self layoutSubviews];
- }
- }];
- }
- else {
- self.contentOffset = targetOffset;
- self.contentSize = contentRect.size;
- applyNewLayoutBlock();
- freeUnusedViews();
- }
- }
- }
- - (void)setCollectionViewLayout:(PSTCollectionViewLayout *)layout {
- [self setCollectionViewLayout:layout animated:NO];
- }
- - (id<PSTCollectionViewDelegate>)delegate {
- return self.extVars.collectionViewDelegate;
- }
- - (void)setDelegate:(id<PSTCollectionViewDelegate>)delegate {
- self.extVars.collectionViewDelegate = delegate;
- // Managing the Selected Cells
- _collectionViewFlags.delegateShouldSelectItemAtIndexPath = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:shouldSelectItemAtIndexPath:)];
- _collectionViewFlags.delegateDidSelectItemAtIndexPath = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:didSelectItemAtIndexPath:)];
- _collectionViewFlags.delegateShouldDeselectItemAtIndexPath = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:shouldDeselectItemAtIndexPath:)];
- _collectionViewFlags.delegateDidDeselectItemAtIndexPath = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:didDeselectItemAtIndexPath:)];
- // Managing Cell Highlighting
- _collectionViewFlags.delegateShouldHighlightItemAtIndexPath = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:shouldHighlightItemAtIndexPath:)];
- _collectionViewFlags.delegateDidHighlightItemAtIndexPath = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:didHighlightItemAtIndexPath:)];
- _collectionViewFlags.delegateDidUnhighlightItemAtIndexPath = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:didUnhighlightItemAtIndexPath:)];
- // Tracking the Removal of Views
- _collectionViewFlags.delegateDidEndDisplayingCell = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:didEndDisplayingCell:forItemAtIndexPath:)];
- _collectionViewFlags.delegateDidEndDisplayingSupplementaryView = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:didEndDisplayingSupplementaryView:forElementOfKind:atIndexPath:)];
- // Managing Actions for Cells
- _collectionViewFlags.delegateSupportsMenus = (unsigned int)[self.delegate respondsToSelector:@selector(collectionView:shouldShowMenuForItemAtIndexPath:)];
- // 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.
- // The flag names are guesses and are there for documentation purposes.
- // _collectionViewFlags.delegateCanPerformActionForItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:canPerformAction:forItemAtIndexPath:withSender:)];
- // _collectionViewFlags.delegatePerformActionForItemAtIndexPath = [self.delegate respondsToSelector:@selector(collectionView:performAction:forItemAtIndexPath:withSender:)];
- }
- // Might be overkill since two are required and two are handled by PSTCollectionViewData leaving only one flag we actually need to check for
- - (void)setDataSource:(id<PSTCollectionViewDataSource>)dataSource {
- if (dataSource != _dataSource) {
- _dataSource = dataSource;
- // Getting Item and Section Metrics
- _collectionViewFlags.dataSourceNumberOfSections = (unsigned int)[_dataSource respondsToSelector:@selector(numberOfSectionsInCollectionView:)];
- // Getting Views for Items
- _collectionViewFlags.dataSourceViewForSupplementaryElement = (unsigned int)[_dataSource respondsToSelector:@selector(collectionView:viewForSupplementaryElementOfKind:atIndexPath:)];
- }
- }
- - (BOOL)allowsSelection {
- return _collectionViewFlags.allowsSelection;
- }
- - (void)setAllowsSelection:(BOOL)allowsSelection {
- _collectionViewFlags.allowsSelection = (unsigned int)allowsSelection;
- }
- - (BOOL)allowsMultipleSelection {
- return _collectionViewFlags.allowsMultipleSelection;
- }
- - (void)setAllowsMultipleSelection:(BOOL)allowsMultipleSelection {
- _collectionViewFlags.allowsMultipleSelection = (unsigned int)allowsMultipleSelection;
- // Deselect all objects if allows multiple selection is false
- if (!allowsMultipleSelection && _indexPathsForSelectedItems.count) {
- // Note: Apple's implementation leaves a mostly random item selected. Presumably they
- // have a good reason for this, but I guess it's just skipping the last or first index.
- for (NSIndexPath *selectedIndexPath in [_indexPathsForSelectedItems copy]) {
- if (_indexPathsForSelectedItems.count == 1) continue;
- [self deselectItemAtIndexPath:selectedIndexPath animated:YES notifyDelegate:YES];
- }
- }
- }
- - (CGRect)visibleBoundRects {
- // in original UICollectionView implementation they
- // check for _visibleBounds and can union self.bounds
- // with this value. Don't know the meaning of _visibleBounds however.
- return self.bounds;
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Private
- - (PSTCollectionViewExt *)extVars {
- return objc_getAssociatedObject(self, &kPSTColletionViewExt);
- }
- - (void)invalidateLayout {
- [self.collectionViewLayout invalidateLayout];
- [self.collectionViewData invalidate]; // invalidate layout cache
- }
- // update currently visible cells, fetches new cells if needed
- // TODO: use now parameter.
- - (void)updateVisibleCellsNow:(BOOL)now {
- NSArray *layoutAttributesArray = [_collectionViewData layoutAttributesForElementsInRect:self.bounds];
- if (layoutAttributesArray == nil || layoutAttributesArray.count == 0) {
- // If our layout source isn't providing any layout information, we should just
- // stop, otherwise we'll blow away all the currently existing cells.
- return;
- }
- // create ItemKey/Attributes dictionary
- NSMutableDictionary *itemKeysToAddDict = [NSMutableDictionary dictionary];
- // Add new cells.
- for (PSTCollectionViewLayoutAttributes *layoutAttributes in layoutAttributesArray) {
- PSTCollectionViewItemKey *itemKey = [PSTCollectionViewItemKey collectionItemKeyForLayoutAttributes:layoutAttributes];
- itemKeysToAddDict[itemKey] = layoutAttributes;
- // check if cell is in visible dict; add it if not.
- PSTCollectionReusableView *view = _allVisibleViewsDict[itemKey];
- if (!view) {
- if (itemKey.type == PSTCollectionViewItemTypeCell) {
- view = [self createPreparedCellForItemAtIndexPath:itemKey.indexPath withLayoutAttributes:layoutAttributes];
- }else if (itemKey.type == PSTCollectionViewItemTypeSupplementaryView) {
- view = [self createPreparedSupplementaryViewForElementOfKind:layoutAttributes.representedElementKind
- atIndexPath:layoutAttributes.indexPath
- withLayoutAttributes:layoutAttributes];
- }else if (itemKey.type == PSTCollectionViewItemTypeDecorationView) {
- view = [self dequeueReusableOrCreateDecorationViewOfKind:layoutAttributes.representedElementKind forIndexPath:layoutAttributes.indexPath];
- }
- // Supplementary views are optional
- if (view) {
- _allVisibleViewsDict[itemKey] = view;
- [self addControlledSubview:view];
- // Always apply attributes. Fixes #203.
- [view applyLayoutAttributes:layoutAttributes];
- }
- }else {
- // just update cell
- [view applyLayoutAttributes:layoutAttributes];
- }
- }
- // Detect what items should be removed and queued back.
- NSMutableSet *allVisibleItemKeys = [NSMutableSet setWithArray:[_allVisibleViewsDict allKeys]];
- [allVisibleItemKeys minusSet:[NSSet setWithArray:[itemKeysToAddDict allKeys]]];
- // Finally remove views that have not been processed and prepare them for re-use.
- for (PSTCollectionViewItemKey *itemKey in allVisibleItemKeys) {
- PSTCollectionReusableView *reusableView = _allVisibleViewsDict[itemKey];
- if (reusableView) {
- [reusableView removeFromSuperview];
- [_allVisibleViewsDict removeObjectForKey:itemKey];
- if (itemKey.type == PSTCollectionViewItemTypeCell) {
- if (_collectionViewFlags.delegateDidEndDisplayingCell) {
- [self.delegate collectionView:self didEndDisplayingCell:(PSTCollectionViewCell *)reusableView forItemAtIndexPath:itemKey.indexPath];
- }
- [self reuseCell:(PSTCollectionViewCell *)reusableView];
- }
- else if (itemKey.type == PSTCollectionViewItemTypeSupplementaryView) {
- if (_collectionViewFlags.delegateDidEndDisplayingSupplementaryView) {
- [self.delegate collectionView:self didEndDisplayingSupplementaryView:reusableView forElementOfKind:itemKey.identifier atIndexPath:itemKey.indexPath];
- }
- [self reuseSupplementaryView:reusableView];
- }
- else if (itemKey.type == PSTCollectionViewItemTypeDecorationView) {
- [self reuseDecorationView:reusableView];
- }
- }
- }
- }
- // fetches a cell from the dataSource and sets the layoutAttributes
- - (PSTCollectionViewCell *)createPreparedCellForItemAtIndexPath:(NSIndexPath *)indexPath withLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes {
- PSTCollectionViewCell *cell = [self.dataSource collectionView:self cellForItemAtIndexPath:indexPath];
- // Apply attributes
- [cell applyLayoutAttributes:layoutAttributes];
- // reset selected/highlight state
- [cell setHighlighted:[_indexPathsForHighlightedItems containsObject:indexPath]];
- [cell setSelected:[_indexPathsForSelectedItems containsObject:indexPath]];
- // voiceover support
- cell.isAccessibilityElement = YES;
- return cell;
- }
- - (PSTCollectionReusableView *)createPreparedSupplementaryViewForElementOfKind:(NSString *)kind
- atIndexPath:(NSIndexPath *)indexPath
- withLayoutAttributes:(PSTCollectionViewLayoutAttributes *)layoutAttributes {
- if (_collectionViewFlags.dataSourceViewForSupplementaryElement) {
- PSTCollectionReusableView *view = [self.dataSource collectionView:self
- viewForSupplementaryElementOfKind:kind
- atIndexPath:indexPath];
- [view applyLayoutAttributes:layoutAttributes];
- return view;
- }
- return nil;
- }
- // @steipete optimization
- - (void)queueReusableView:(PSTCollectionReusableView *)reusableView inQueue:(NSMutableDictionary *)queue withIdentifier:(NSString *)identifier {
- NSParameterAssert(identifier.length > 0);
- [reusableView removeFromSuperview];
- [reusableView prepareForReuse];
- // enqueue cell
- NSMutableArray *reuseableViews = queue[identifier];
- if (!reuseableViews) {
- reuseableViews = [NSMutableArray array];
- queue[identifier] = reuseableViews;
- }
- [reuseableViews addObject:reusableView];
- }
- // enqueue cell for reuse
- - (void)reuseCell:(PSTCollectionViewCell *)cell {
- [self queueReusableView:cell inQueue:_cellReuseQueues withIdentifier:cell.reuseIdentifier];
- }
- // enqueue supplementary view for reuse
- - (void)reuseSupplementaryView:(PSTCollectionReusableView *)supplementaryView {
- NSString *kindAndIdentifier = [NSString stringWithFormat:@"%@/%@", supplementaryView.layoutAttributes.elementKind, supplementaryView.reuseIdentifier];
- [self queueReusableView:supplementaryView inQueue:_supplementaryViewReuseQueues withIdentifier:kindAndIdentifier];
- }
- // enqueue decoration view for reuse
- - (void)reuseDecorationView:(PSTCollectionReusableView *)decorationView {
- [self queueReusableView:decorationView inQueue:_decorationViewReuseQueues withIdentifier:decorationView.reuseIdentifier];
- }
- - (void)addControlledSubview:(PSTCollectionReusableView *)subview {
- // avoids placing views above the scroll indicator
- // If the collection view is not displaying scrollIndicators then self.subviews.count can be 0.
- // We take the max to ensure we insert at a non negative index because a negative index will silently fail to insert the view
- NSInteger insertionIndex = MAX((NSInteger)(self.subviews.count - (self.dragging ? 1 : 0)), 0);
- [self insertSubview:subview atIndex:insertionIndex];
- UIView *scrollIndicatorView = nil;
- if (self.dragging) {
- scrollIndicatorView = [self.subviews lastObject];
- }
- NSMutableArray *floatingViews = [[NSMutableArray alloc] init];
- for (UIView *uiView in self.subviews) {
- if ([uiView isKindOfClass:PSTCollectionReusableView.class] && [[(PSTCollectionReusableView *)uiView layoutAttributes] zIndex] > 0) {
- [floatingViews addObject:uiView];
- }
- }
- [floatingViews sortUsingComparator:^NSComparisonResult(PSTCollectionReusableView *obj1, PSTCollectionReusableView *obj2) {
- CGFloat z1 = [[obj1 layoutAttributes] zIndex];
- CGFloat z2 = [[obj2 layoutAttributes] zIndex];
- if (z1 > z2) {
- return (NSComparisonResult)NSOrderedDescending;
- }else if (z1 < z2) {
- return (NSComparisonResult)NSOrderedAscending;
- }else {
- return (NSComparisonResult)NSOrderedSame;
- }
- }];
- for (PSTCollectionReusableView *uiView in floatingViews) {
- [self bringSubviewToFront:uiView];
- }
- if (floatingViews.count && scrollIndicatorView) {
- [self bringSubviewToFront:scrollIndicatorView];
- }
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Updating grid internal functionality
- - (void)suspendReloads {
- _reloadingSuspendedCount++;
- }
- - (void)resumeReloads {
- if (0 < _reloadingSuspendedCount) _reloadingSuspendedCount--;
- }
- - (NSMutableArray *)arrayForUpdateAction:(PSTCollectionUpdateAction)updateAction {
- NSMutableArray *updateActions = nil;
- switch (updateAction) {
- case PSTCollectionUpdateActionInsert:
- if (!_insertItems) _insertItems = [NSMutableArray new];
- updateActions = _insertItems;
- break;
- case PSTCollectionUpdateActionDelete:
- if (!_deleteItems) _deleteItems = [NSMutableArray new];
- updateActions = _deleteItems;
- break;
- case PSTCollectionUpdateActionMove:
- if (!_moveItems) _moveItems = [NSMutableArray new];
- updateActions = _moveItems;
- break;
- case PSTCollectionUpdateActionReload:
- if (!_reloadItems) _reloadItems = [NSMutableArray new];
- updateActions = _reloadItems;
- break;
- default: break;
- }
- return updateActions;
- }
- - (void)prepareLayoutForUpdates {
- NSMutableArray *array = [[NSMutableArray alloc] init];
- [array addObjectsFromArray:[_originalDeleteItems sortedArrayUsingSelector:@selector(inverseCompareIndexPaths:)]];
- [array addObjectsFromArray:[_originalInsertItems sortedArrayUsingSelector:@selector(compareIndexPaths:)]];
- [array addObjectsFromArray:[_reloadItems sortedArrayUsingSelector:@selector(compareIndexPaths:)]];
- [array addObjectsFromArray:[_moveItems sortedArrayUsingSelector:@selector(compareIndexPaths:)]];
- [_layout prepareForCollectionViewUpdates:array];
- }
- - (void)updateWithItems:(NSArray *)items {
- [self prepareLayoutForUpdates];
- NSMutableArray *animations = [[NSMutableArray alloc] init];
- NSMutableDictionary *newAllVisibleView = [[NSMutableDictionary alloc] init];
- NSMutableDictionary *viewsToRemove = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- [NSMutableArray array], @(PSTCollectionViewItemTypeCell),
- [NSMutableArray array], @(PSTCollectionViewItemTypeDecorationView),
- [NSMutableArray array], @(PSTCollectionViewItemTypeSupplementaryView), nil];
- for (PSTCollectionViewUpdateItem *updateItem in items) {
- if (updateItem.isSectionOperation && updateItem.updateAction != PSTCollectionUpdateActionDelete) continue;
- if (updateItem.isSectionOperation && updateItem.updateAction == PSTCollectionUpdateActionDelete) {
- NSInteger numberOfBeforeSection = [_update[@"oldModel"] numberOfItemsInSection:updateItem.indexPathBeforeUpdate.section];
- for (NSInteger i = 0; i < numberOfBeforeSection; i++) {
- NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:updateItem.indexPathBeforeUpdate.section];
- PSTCollectionViewLayoutAttributes *finalAttrs = [_layout finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
- PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath];
- PSTCollectionReusableView *view = _allVisibleViewsDict[key];
- if (view) {
- PSTCollectionViewLayoutAttributes *startAttrs = view.layoutAttributes;
- if (!finalAttrs) {
- finalAttrs = [startAttrs copy];
- finalAttrs.alpha = 0;
- }
- [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs, @"newLayoutInfos" : finalAttrs}];
- [_allVisibleViewsDict removeObjectForKey:key];
- [(NSMutableArray *)viewsToRemove[@(key.type)] addObject:view];
- }
- }
- continue;
- }
- if (updateItem.updateAction == PSTCollectionUpdateActionDelete) {
- NSIndexPath *indexPath = updateItem.indexPathBeforeUpdate;
- PSTCollectionViewLayoutAttributes *finalAttrs = [_layout finalLayoutAttributesForDisappearingItemAtIndexPath:indexPath];
- PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath];
- PSTCollectionReusableView *view = _allVisibleViewsDict[key];
- if (view) {
- PSTCollectionViewLayoutAttributes *startAttrs = view.layoutAttributes;
- if (!finalAttrs) {
- finalAttrs = [startAttrs copy];
- finalAttrs.alpha = 0;
- }
- [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs, @"newLayoutInfos" : finalAttrs}];
- [_allVisibleViewsDict removeObjectForKey:key];
- [(NSMutableArray *)viewsToRemove[@(key.type)] addObject:view];
- }
- }
- else if (updateItem.updateAction == PSTCollectionUpdateActionInsert) {
- NSIndexPath *indexPath = updateItem.indexPathAfterUpdate;
- PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPath];
- PSTCollectionViewLayoutAttributes *startAttrs = [_layout initialLayoutAttributesForAppearingItemAtIndexPath:indexPath];
- PSTCollectionViewLayoutAttributes *finalAttrs = [_layout layoutAttributesForItemAtIndexPath:indexPath];
- CGRect startRect = startAttrs.frame;
- CGRect finalRect = finalAttrs.frame;
- if (CGRectIntersectsRect(self.visibleBoundRects, startRect) || CGRectIntersectsRect(self.visibleBoundRects, finalRect)) {
- if (!startAttrs) {
- startAttrs = [finalAttrs copy];
- startAttrs.alpha = 0;
- }
- PSTCollectionReusableView *view = [self createPreparedCellForItemAtIndexPath:indexPath
- withLayoutAttributes:startAttrs];
- [self addControlledSubview:view];
- newAllVisibleView[key] = view;
- [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs, @"newLayoutInfos" : finalAttrs}];
- }
- }
- else if (updateItem.updateAction == PSTCollectionUpdateActionMove) {
- NSIndexPath *indexPathBefore = updateItem.indexPathBeforeUpdate;
- NSIndexPath *indexPathAfter = updateItem.indexPathAfterUpdate;
- PSTCollectionViewItemKey *keyBefore = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPathBefore];
- PSTCollectionViewItemKey *keyAfter = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:indexPathAfter];
- PSTCollectionReusableView *view = _allVisibleViewsDict[keyBefore];
- PSTCollectionViewLayoutAttributes *startAttrs = nil;
- PSTCollectionViewLayoutAttributes *finalAttrs = [_layout layoutAttributesForItemAtIndexPath:indexPathAfter];
- if (view) {
- startAttrs = view.layoutAttributes;
- [_allVisibleViewsDict removeObjectForKey:keyBefore];
- newAllVisibleView[keyAfter] = view;
- }
- else {
- startAttrs = [finalAttrs copy];
- startAttrs.alpha = 0;
- view = [self createPreparedCellForItemAtIndexPath:indexPathAfter withLayoutAttributes:startAttrs];
- [self addControlledSubview:view];
- newAllVisibleView[keyAfter] = view;
- }
- [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs, @"newLayoutInfos" : finalAttrs}];
- }
- }
- for (PSTCollectionViewItemKey *key in [_allVisibleViewsDict keyEnumerator]) {
- PSTCollectionReusableView *view = _allVisibleViewsDict[key];
- if (key.type == PSTCollectionViewItemTypeCell) {
- NSUInteger oldGlobalIndex = [_update[@"oldModel"] globalIndexForItemAtIndexPath:key.indexPath];
- NSArray *oldToNewIndexMap = _update[@"oldToNewIndexMap"];
- NSUInteger newGlobalIndex = NSNotFound;
- if (NSNotFound != oldGlobalIndex && oldGlobalIndex < oldToNewIndexMap.count) {
- newGlobalIndex = [oldToNewIndexMap[oldGlobalIndex] unsignedIntegerValue];
- }
- NSIndexPath *newIndexPath = newGlobalIndex == NSNotFound ? nil : [_update[@"newModel"] indexPathForItemAtGlobalIndex:(int)newGlobalIndex];
- NSIndexPath *oldIndexPath = oldGlobalIndex == NSNotFound ? nil : [_update[@"oldModel"] indexPathForItemAtGlobalIndex:(int)oldGlobalIndex];
- if (newIndexPath) {
- PSTCollectionViewLayoutAttributes *startAttrs = nil;
- PSTCollectionViewLayoutAttributes *finalAttrs = nil;
- startAttrs = [_layout initialLayoutAttributesForAppearingItemAtIndexPath:oldIndexPath];
- finalAttrs = [_layout layoutAttributesForItemAtIndexPath:newIndexPath];
- NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:@{@"view" : view}];
- if (startAttrs) dic[@"previousLayoutInfos"] = startAttrs;
- if (finalAttrs) dic[@"newLayoutInfos"] = finalAttrs;
- [animations addObject:dic];
- PSTCollectionViewItemKey *newKey = [key copy];
- [newKey setIndexPath:newIndexPath];
- newAllVisibleView[newKey] = view;
- }
- }else if (key.type == PSTCollectionViewItemTypeSupplementaryView) {
- PSTCollectionViewLayoutAttributes *startAttrs = nil;
- PSTCollectionViewLayoutAttributes *finalAttrs = nil;
- startAttrs = view.layoutAttributes;
- finalAttrs = [_layout layoutAttributesForSupplementaryViewOfKind:view.layoutAttributes.representedElementKind atIndexPath:key.indexPath];
- NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithDictionary:@{@"view" : view}];
- if (startAttrs) dic[@"previousLayoutInfos"] = startAttrs;
- if (finalAttrs) dic[@"newLayoutInfos"] = finalAttrs;
- [animations addObject:dic];
- PSTCollectionViewItemKey *newKey = [key copy];
- newAllVisibleView[newKey] = view;
- }
- }
- NSArray *allNewlyVisibleItems = [_layout layoutAttributesForElementsInRect:self.visibleBoundRects];
- for (PSTCollectionViewLayoutAttributes *attrs in allNewlyVisibleItems) {
- PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForLayoutAttributes:attrs];
- if (key.type == PSTCollectionViewItemTypeCell && ![[newAllVisibleView allKeys] containsObject:key]) {
- PSTCollectionViewLayoutAttributes *startAttrs =
- [_layout initialLayoutAttributesForAppearingItemAtIndexPath:attrs.indexPath];
- PSTCollectionReusableView *view = [self createPreparedCellForItemAtIndexPath:attrs.indexPath
- withLayoutAttributes:startAttrs];
- [self addControlledSubview:view];
- newAllVisibleView[key] = view;
- [animations addObject:@{@"view" : view, @"previousLayoutInfos" : startAttrs ? startAttrs : attrs, @"newLayoutInfos" : attrs}];
- }
- }
- //In here I think it doesn't need the animation but the transaction, it would cause some issue of display.
- //I resolve the bug when user insert the new sections, the cell will display with a blink animation at the first operation.
- //But I don't know why the next operation wouldn't reproduction in the pre version.
- _allVisibleViewsDict = newAllVisibleView;
- for (NSDictionary *animation in animations) {
- PSTCollectionReusableView *view = animation[@"view"];
- PSTCollectionViewLayoutAttributes *attr = animation[@"previousLayoutInfos"];
- [view applyLayoutAttributes:attr];
- };
- _collectionViewFlags.updatingLayout = YES;
-
- [CATransaction begin];
- [CATransaction setAnimationDuration:0];
- [CATransaction setCompletionBlock:^{
- // Iterate through all the views that we are going to remove.
- [viewsToRemove enumerateKeysAndObjectsUsingBlock:^(NSNumber *keyObj, NSArray *views, BOOL *stop) {
- PSTCollectionViewItemType type = [keyObj unsignedIntegerValue];
- for (PSTCollectionReusableView *view in views) {
- if (type == PSTCollectionViewItemTypeCell) {
- [self reuseCell:(PSTCollectionViewCell *)view];
- } else if (type == PSTCollectionViewItemTypeSupplementaryView) {
- [self reuseSupplementaryView:view];
- } else if (type == PSTCollectionViewItemTypeDecorationView) {
- [self reuseDecorationView:view];
- }
- }
- }];
-
- _collectionViewFlags.updatingLayout = NO;
-
- //In here I think when the block is called, the flag is YES. So the _updateCopletionHandler's paramer is YES.
- if (_updateCompletionHandler) {
- _updateCompletionHandler(YES);
- _updateCompletionHandler = nil;
- }
- }];
-
- for (NSDictionary *animation in animations) {
- PSTCollectionReusableView *view = animation[@"view"];
- PSTCollectionViewLayoutAttributes *attrs = animation[@"newLayoutInfos"];
- [view applyLayoutAttributes:attrs];
- }
- [CATransaction commit];
-
- [_layout finalizeCollectionViewUpdates];
- }
- - (void)setupCellAnimations {
- [self updateVisibleCellsNow:YES];
- [self suspendReloads];
- _collectionViewFlags.updating = YES;
- }
- - (void)endItemAnimations {
- _updateCount++;
- PSTCollectionViewData *oldCollectionViewData = _collectionViewData;
- _collectionViewData = [[PSTCollectionViewData alloc] initWithCollectionView:self layout:_layout];
- [_layout invalidateLayout];
- [_collectionViewData prepareToLoadData];
- NSMutableArray *someMutableArr1 = [[NSMutableArray alloc] init];
- NSArray *removeUpdateItems = [[self arrayForUpdateAction:PSTCollectionUpdateActionDelete]
- sortedArrayUsingSelector:@selector(inverseCompareIndexPaths:)];
- NSArray *insertUpdateItems = [[self arrayForUpdateAction:PSTCollectionUpdateActionInsert]
- sortedArrayUsingSelector:@selector(compareIndexPaths:)];
- NSMutableArray *sortedMutableReloadItems = [[_reloadItems sortedArrayUsingSelector:@selector(compareIndexPaths:)] mutableCopy];
- NSMutableArray *sortedMutableMoveItems = [[_moveItems sortedArrayUsingSelector:@selector(compareIndexPaths:)] mutableCopy];
- _originalDeleteItems = [removeUpdateItems copy];
- _originalInsertItems = [insertUpdateItems copy];
- NSMutableArray *someMutableArr2 = [[NSMutableArray alloc] init];
- NSMutableArray *someMutableArr3 = [[NSMutableArray alloc] init];
- NSMutableDictionary *operations = [[NSMutableDictionary alloc] init];
- for (PSTCollectionViewUpdateItem *updateItem in sortedMutableReloadItems) {
- NSAssert(updateItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
- @"attempt to reload item (%@) that doesn't exist (there are only %ld sections before update)",
- updateItem.indexPathBeforeUpdate, (long)[oldCollectionViewData numberOfSections]);
- if (updateItem.indexPathBeforeUpdate.item != NSNotFound) {
- NSAssert(updateItem.indexPathBeforeUpdate.item < [oldCollectionViewData numberOfItemsInSection:updateItem.indexPathBeforeUpdate.section],
- @"attempt to reload item (%@) that doesn't exist (there are only %ld items in section %ld before update)",
- updateItem.indexPathBeforeUpdate,
- (long)[oldCollectionViewData numberOfItemsInSection:updateItem.indexPathBeforeUpdate.section],
- (long)updateItem.indexPathBeforeUpdate.section);
- }
- [someMutableArr2 addObject:[[PSTCollectionViewUpdateItem alloc] initWithAction:PSTCollectionUpdateActionDelete
- forIndexPath:updateItem.indexPathBeforeUpdate]];
- [someMutableArr3 addObject:[[PSTCollectionViewUpdateItem alloc] initWithAction:PSTCollectionUpdateActionInsert
- forIndexPath:updateItem.indexPathAfterUpdate]];
- }
- NSMutableArray *sortedDeletedMutableItems = [[_deleteItems sortedArrayUsingSelector:@selector(inverseCompareIndexPaths:)] mutableCopy];
- NSMutableArray *sortedInsertMutableItems = [[_insertItems sortedArrayUsingSelector:@selector(compareIndexPaths:)] mutableCopy];
- for (PSTCollectionViewUpdateItem *deleteItem in sortedDeletedMutableItems) {
- if ([deleteItem isSectionOperation]) {
- NSAssert(deleteItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
- @"attempt to delete section (%ld) that doesn't exist (there are only %ld sections before update)",
- (long)deleteItem.indexPathBeforeUpdate.section,
- (long)[oldCollectionViewData numberOfSections]);
- for (PSTCollectionViewUpdateItem *moveItem in sortedMutableMoveItems) {
- if (moveItem.indexPathBeforeUpdate.section == deleteItem.indexPathBeforeUpdate.section) {
- if (moveItem.isSectionOperation)
- NSAssert(NO, @"attempt to delete and move from the same section %ld", (long)deleteItem.indexPathBeforeUpdate.section);
- else
- NSAssert(NO, @"attempt to delete and move from the same section (%@)", moveItem.indexPathBeforeUpdate);
- }
- }
- }else {
- NSAssert(deleteItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
- @"attempt to delete item (%@) that doesn't exist (there are only %ld sections before update)",
- deleteItem.indexPathBeforeUpdate,
- (long)[oldCollectionViewData numberOfSections]);
- NSAssert(deleteItem.indexPathBeforeUpdate.item < [oldCollectionViewData numberOfItemsInSection:deleteItem.indexPathBeforeUpdate.section],
- @"attempt to delete item (%@) that doesn't exist (there are only %ld items in section%ld before update)",
- deleteItem.indexPathBeforeUpdate,
- (long)[oldCollectionViewData numberOfItemsInSection:deleteItem.indexPathBeforeUpdate.section],
- (long)deleteItem.indexPathBeforeUpdate.section);
- for (PSTCollectionViewUpdateItem *moveItem in sortedMutableMoveItems) {
- NSAssert(![deleteItem.indexPathBeforeUpdate isEqual:moveItem.indexPathBeforeUpdate],
- @"attempt to delete and move the same item (%@)", deleteItem.indexPathBeforeUpdate);
- }
- if (!operations[@(deleteItem.indexPathBeforeUpdate.section)])
- operations[@(deleteItem.indexPathBeforeUpdate.section)] = [NSMutableDictionary dictionary];
- operations[@(deleteItem.indexPathBeforeUpdate.section)][@"deleted"] =
- @([operations[@(deleteItem.indexPathBeforeUpdate.section)][@"deleted"] intValue] + 1);
- }
- }
- for (NSUInteger i = 0; i < sortedInsertMutableItems.count; i++) {
- PSTCollectionViewUpdateItem *insertItem = sortedInsertMutableItems[i];
- NSIndexPath *indexPath = insertItem.indexPathAfterUpdate;
- BOOL sectionOperation = [insertItem isSectionOperation];
- if (sectionOperation) {
- NSAssert([indexPath section] < [_collectionViewData numberOfSections],
- @"attempt to insert %ld but there are only %ld sections after update",
- (long)[indexPath section], (long)[_collectionViewData numberOfSections]);
- for (PSTCollectionViewUpdateItem *moveItem in sortedMutableMoveItems) {
- if ([moveItem.indexPathAfterUpdate isEqual:indexPath]) {
- if (moveItem.isSectionOperation)
- NSAssert(NO, @"attempt to perform an insert and a move to the same section (%ld)", (long)indexPath.section);
- }
- }
- NSUInteger j = i + 1;
- while (j < sortedInsertMutableItems.count) {
- PSTCollectionViewUpdateItem *nextInsertItem = sortedInsertMutableItems[j];
- if (nextInsertItem.indexPathAfterUpdate.section == indexPath.section) {
- NSAssert(nextInsertItem.indexPathAfterUpdate.item < [_collectionViewData numberOfItemsInSection:indexPath.section],
- @"attempt to insert item %ld into section %ld, but there are only %ld items in section %ld after the update",
- (long)nextInsertItem.indexPathAfterUpdate.item,
- (long)indexPath.section,
- (long)[_collectionViewData numberOfItemsInSection:indexPath.section],
- (long)indexPath.section);
- [sortedInsertMutableItems removeObjectAtIndex:j];
- }
- else break;
- }
- }else {
- NSAssert(indexPath.item < [_collectionViewData numberOfItemsInSection:indexPath.section],
- @"attempt to insert item to (%@) but there are only %ld items in section %ld after update",
- indexPath,
- (long)[_collectionViewData numberOfItemsInSection:indexPath.section],
- (long)indexPath.section);
- if (!operations[@(indexPath.section)])
- operations[@(indexPath.section)] = [NSMutableDictionary dictionary];
- operations[@(indexPath.section)][@"inserted"] =
- @([operations[@(indexPath.section)][@"inserted"] intValue] + 1);
- }
- }
- for (PSTCollectionViewUpdateItem *sortedItem in sortedMutableMoveItems) {
- if (sortedItem.isSectionOperation) {
- NSAssert(sortedItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
- @"attempt to move section (%ld) that doesn't exist (%ld sections before update)",
- (long)sortedItem.indexPathBeforeUpdate.section,
- (long)[oldCollectionViewData numberOfSections]);
- NSAssert(sortedItem.indexPathAfterUpdate.section < [_collectionViewData numberOfSections],
- @"attempt to move section to %ld but there are only %ld sections after update",
- (long)sortedItem.indexPathAfterUpdate.section,
- (long)[_collectionViewData numberOfSections]);
- }else {
- NSAssert(sortedItem.indexPathBeforeUpdate.section < [oldCollectionViewData numberOfSections],
- @"attempt to move item (%@) that doesn't exist (%ld sections before update)",
- sortedItem, (long)[oldCollectionViewData numberOfSections]);
- NSAssert(sortedItem.indexPathBeforeUpdate.item < [oldCollectionViewData numberOfItemsInSection:sortedItem.indexPathBeforeUpdate.section],
- @"attempt to move item (%@) that doesn't exist (%ld items in section %ld before update)",
- sortedItem,
- (long)[oldCollectionViewData numberOfItemsInSection:sortedItem.indexPathBeforeUpdate.section],
- (long)sortedItem.indexPathBeforeUpdate.section);
- NSAssert(sortedItem.indexPathAfterUpdate.section < [_collectionViewData numberOfSections],
- @"attempt to move item to (%@) but there are only %ld sections after update",
- sortedItem.indexPathAfterUpdate,
- (long)[_collectionViewData numberOfSections]);
- NSAssert(sortedItem.indexPathAfterUpdate.item < [_collectionViewData numberOfItemsInSection:sortedItem.indexPathAfterUpdate.section],
- @"attempt to move item to (%@) but there are only %ld items in section %ld after update",
- sortedItem,
- (long)[_collectionViewData numberOfItemsInSection:sortedItem.indexPathAfterUpdate.section],
- (long)sortedItem.indexPathAfterUpdate.section);
- }
- if (!operations[@(sortedItem.indexPathBeforeUpdate.section)])
- operations[@(sortedItem.indexPathBeforeUpdate.section)] = [NSMutableDictionary dictionary];
- if (!operations[@(sortedItem.indexPathAfterUpdate.section)])
- operations[@(sortedItem.indexPathAfterUpdate.section)] = [NSMutableDictionary dictionary];
- operations[@(sortedItem.indexPathBeforeUpdate.section)][@"movedOut"] =
- @([operations[@(sortedItem.indexPathBeforeUpdate.section)][@"movedOut"] intValue] + 1);
- operations[@(sortedItem.indexPathAfterUpdate.section)][@"movedIn"] =
- @([operations[@(sortedItem.indexPathAfterUpdate.section)][@"movedIn"] intValue] + 1);
- }
- #if !defined NS_BLOCK_ASSERTIONS
- for (NSNumber *sectionKey in [operations keyEnumerator]) {
- NSInteger section = [sectionKey integerValue];
- NSInteger insertedCount = [operations[sectionKey][@"inserted"] integerValue];
- NSInteger deletedCount = [operations[sectionKey][@"deleted"] integerValue];
- NSInteger movedInCount = [operations[sectionKey][@"movedIn"] integerValue];
- NSInteger movedOutCount = [operations[sectionKey][@"movedOut"] integerValue];
- NSAssert([oldCollectionViewData numberOfItemsInSection:section] + insertedCount - deletedCount + movedInCount - movedOutCount ==
- [_collectionViewData numberOfItemsInSection:section],
- @"invalid update in section %ld: number of items after update (%ld) should be equal to the number of items before update (%ld) "\
- "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)",
- (long)section,
- (long)[_collectionViewData numberOfItemsInSection:section],
- (long)[oldCollectionViewData numberOfItemsInSection:section],
- (long)insertedCount, (long)deletedCount, (long)movedInCount, (long)movedOutCount);
- }
- #endif
- [someMutableArr2 addObjectsFromArray:sortedDeletedMutableItems];
- [someMutableArr3 addObjectsFromArray:sortedInsertMutableItems];
- [someMutableArr1 addObjectsFromArray:[someMutableArr2 sortedArrayUsingSelector:@selector(inverseCompareIndexPaths:)]];
- [someMutableArr1 addObjectsFromArray:sortedMutableMoveItems];
- [someMutableArr1 addObjectsFromArray:[someMutableArr3 sortedArrayUsingSelector:@selector(compareIndexPaths:)]];
- NSMutableArray *layoutUpdateItems = [[NSMutableArray alloc] init];
- [layoutUpdateItems addObjectsFromArray:sortedDeletedMutableItems];
- [layoutUpdateItems addObjectsFromArray:sortedMutableMoveItems];
- [layoutUpdateItems addObjectsFromArray:sortedInsertMutableItems];
- NSMutableArray *newModel = [NSMutableArray array];
- for (NSInteger i = 0; i < [oldCollectionViewData numberOfSections]; i++) {
- NSMutableArray *sectionArr = [NSMutableArray array];
- for (NSInteger j = 0; j < [oldCollectionViewData numberOfItemsInSection:i]; j++)
- [sectionArr addObject:@([oldCollectionViewData globalIndexForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:i]])];
- [newModel addObject:sectionArr];
- }
- for (PSTCollectionViewUpdateItem *updateItem in layoutUpdateItems) {
- switch (updateItem.updateAction) {
- case PSTCollectionUpdateActionDelete: {
- if (updateItem.isSectionOperation) {
- // section updates are ignored anyway in animation code. If not commented, mixing rows and section deletion causes crash in else below
- // [newModel removeObjectAtIndex:updateItem.indexPathBeforeUpdate.section];
- }else {
- [(NSMutableArray *)newModel[(NSUInteger)updateItem.indexPathBeforeUpdate.section]
- removeObjectAtIndex:(NSUInteger)updateItem.indexPathBeforeUpdate.item];
- }
- }
- break;
- case PSTCollectionUpdateActionInsert: {
- if (updateItem.isSectionOperation) {
- [newModel insertObject:[[NSMutableArray alloc] init]
- atIndex:(NSUInteger)updateItem.indexPathAfterUpdate.section];
- }else {
- [(NSMutableArray *)newModel[(NSUInteger)updateItem.indexPathAfterUpdate.section]
- insertObject:@(NSNotFound)
- atIndex:(NSUInteger)updateItem.indexPathAfterUpdate.item];
- }
- }
- break;
- case PSTCollectionUpdateActionMove: {
- if (updateItem.isSectionOperation) {
- id section = newModel[(NSUInteger)updateItem.indexPathBeforeUpdate.section];
- [newModel insertObject:section atIndex:(NSUInteger)updateItem.indexPathAfterUpdate.section];
- }
- else {
- id object = @([oldCollectionViewData globalIndexForItemAtIndexPath:updateItem.indexPathBeforeUpdate]);
- [newModel[(NSUInteger)updateItem.indexPathBeforeUpdate.section] removeObject:object];
- [newModel[(NSUInteger)updateItem.indexPathAfterUpdate.section] insertObject:object
- atIndex:(NSUInteger)updateItem.indexPathAfterUpdate.item];
- }
- }
- break;
- default: break;
- }
- }
- NSMutableArray *oldToNewMap = [NSMutableArray arrayWithCapacity:(NSUInteger)[oldCollectionViewData numberOfItems]];
- NSMutableArray *newToOldMap = [NSMutableArray arrayWithCapacity:(NSUInteger)[_collectionViewData numberOfItems]];
- for (NSInteger i = 0; i < [oldCollectionViewData numberOfItems]; i++)
- [oldToNewMap addObject:@(NSNotFound)];
- for (NSInteger i = 0; i < [_collectionViewData numberOfItems]; i++)
- [newToOldMap addObject:@(NSNotFound)];
- for (NSUInteger i = 0; i < newModel.count; i++) {
- NSMutableArray *section = newModel[i];
- for (NSUInteger j = 0; j < section.count; j++) {
- NSUInteger newGlobalIndex = [_collectionViewData globalIndexForItemAtIndexPath:[NSIndexPath indexPathForItem:(NSInteger)j inSection:(NSInteger)i]];
- if ([section[j] integerValue] != NSNotFound)
- oldToNewMap[[section[j] unsignedIntegerValue]] = @(newGlobalIndex);
- if (newGlobalIndex != NSNotFound)
- newToOldMap[newGlobalIndex] = section[j];
- }
- }
- _update = @{@"oldModel" : oldCollectionViewData, @"newModel" : _collectionViewData, @"oldToNewIndexMap" : oldToNewMap, @"newToOldIndexMap" : newToOldMap};
- [self updateWithItems:someMutableArr1];
- _originalInsertItems = nil;
- _originalDeleteItems = nil;
- _insertItems = nil;
- _deleteItems = nil;
- _moveItems = nil;
- _reloadItems = nil;
- _update = nil;
- _updateCount--;
- _collectionViewFlags.updating = NO;
- [self resumeReloads];
- }
- - (void)updateRowsAtIndexPaths:(NSArray *)indexPaths updateAction:(PSTCollectionUpdateAction)updateAction {
- BOOL updating = _collectionViewFlags.updating;
- if (!updating) [self setupCellAnimations];
- NSMutableArray *array = [self arrayForUpdateAction:updateAction]; //returns appropriate empty array if not exists
- for (NSIndexPath *indexPath in indexPaths) {
- PSTCollectionViewUpdateItem *updateItem = [[PSTCollectionViewUpdateItem alloc] initWithAction:updateAction forIndexPath:indexPath];
- [array addObject:updateItem];
- }
- if (!updating) [self endItemAnimations];
- }
- - (void)updateSections:(NSIndexSet *)sections updateAction:(PSTCollectionUpdateAction)updateAction {
- BOOL updating = _collectionViewFlags.updating;
- if (!updating) [self setupCellAnimations];
- NSMutableArray *updateActions = [self arrayForUpdateAction:updateAction];
- [sections enumerateIndexesUsingBlock:^(NSUInteger section, BOOL *stop) {
- PSTCollectionViewUpdateItem *updateItem = [[PSTCollectionViewUpdateItem alloc] initWithAction:updateAction forIndexPath:[NSIndexPath indexPathForItem:NSNotFound inSection:(NSInteger)section]];
- [updateActions addObject:updateItem];
- }];
- if (!updating) [self endItemAnimations];
- }
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - PSTCollection/UICollection interoperability
- #ifdef kPSUIInteroperabilityEnabled
- #import <objc/message.h>
- - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
- NSMethodSignature *sig = [super methodSignatureForSelector:selector];
- if(!sig) {
- NSString *selString = NSStringFromSelector(selector);
- if ([selString hasPrefix:@"_"]) {
- SEL cleanedSelector = NSSelectorFromString([selString substringFromIndex:1]);
- sig = [super methodSignatureForSelector:cleanedSelector];
- }
- }
- return sig;
- }
- - (void)forwardInvocation:(NSInvocation *)inv {
- NSString *selString = NSStringFromSelector([inv selector]);
- if ([selString hasPrefix:@"_"]) {
- SEL cleanedSelector = NSSelectorFromString([selString substringFromIndex:1]);
- if ([self respondsToSelector:cleanedSelector]) {
- // dynamically add method for faster resolving
- Method newMethod = class_getInstanceMethod(self.class, [inv selector]);
- IMP underscoreIMP = imp_implementationWithBlock(^(id _self) {
- return objc_msgSend(_self, cleanedSelector);
- });
- class_addMethod(self.class, [inv selector], underscoreIMP, method_getTypeEncoding(newMethod));
- // invoke now
- inv.selector = cleanedSelector;
- [inv invokeWithTarget:self];
- }
- }else {
- [super forwardInvocation:inv];
- }
- }
- #endif
- @end
- ///////////////////////////////////////////////////////////////////////////////////////////
- #pragma mark - Runtime Additions to create UICollectionView
- @implementation PSUICollectionView_ @end
- @implementation PSUICollectionViewCell_ @end
- @implementation PSUICollectionReusableView_ @end
- @implementation PSUICollectionViewLayout_ @end
- @implementation PSUICollectionViewFlowLayout_ @end
- @implementation PSUICollectionViewLayoutAttributes_ @end
- @implementation PSUICollectionViewController_ @end
- static void PSTRegisterClasses() {
- NSDictionary *map = @{
- @"UICollectionView": PSUICollectionView_.class,
- @"UICollectionViewCell": PSUICollectionViewCell_.class,
- @"UICollectionReusableView": PSUICollectionReusableView_.class,
- @"UICollectionViewLayout": PSUICollectionViewLayout_.class,
- @"UICollectionViewFlowLayout": PSUICollectionViewFlowLayout_.class,
- @"UICollectionViewLayoutAttributes": PSUICollectionViewLayoutAttributes_.class,
- @"UICollectionViewController": PSUICollectionViewController_.class
- };
- // Ensure that superclass replacement is all-or-nothing for the PSUI*_ types. Either use exclusively
- // UICollectionView*, or exclusively PSTCollectionView*.
- __block BOOL canOverwrite = YES;
- [map enumerateKeysAndObjectsUsingBlock:^(NSString* UIClassName, id PSTClass, BOOL *stop) {
- Class UIClass = NSClassFromString(UIClassName);
- if (UIClass) {
- // Class size need to be the same for class_setSuperclass to work.
- // If the UIKit class is smaller then our subclass, ivars won't clash, so there's no issue.
- long sizeDifference = (long)class_getInstanceSize(UIClass) - class_getInstanceSize(PSTClass);
- if (sizeDifference > 0) {
- canOverwrite = NO;
- NSLog(@"Warning! ivar size mismatch in %@ of %tu bytes - can't change the superclass.", PSTClass, sizeDifference);
- }
- } else {
- canOverwrite = NO;
- // We're most likely on iOS5, the requested UIKit class doesn't exist, so we create it dynamically.
- if ((UIClass = objc_allocateClassPair(PSTClass, UIClassName.UTF8String, 0))) {
- objc_registerClassPair(UIClass);
- }
- }
- }];
- if (canOverwrite) {
- // All UICollectionView types were found and appropriately sized, so it is safe to replace the super-class.
- [map enumerateKeysAndObjectsUsingBlock:^(NSString* UIClassName, id PSTClass, BOOL *stop) {
- Class UIClass = NSClassFromString(UIClassName);
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wdeprecated-declarations"
- // class_setSuperclass is deprecated, but still exists and works on iOS6/7.
- class_setSuperclass(PSTClass, UIClass);
- #pragma clang diagnostic pop
- }];
- }
- }
- // Create subclasses that pose as UICollectionView et al, if not available at runtime.
- __attribute__((constructor)) static void PSTCreateUICollectionViewClasses(void) {
- if (objc_getClass("PSTCollectionViewDisableForwardToUICollectionViewSentinel")) return;
- @autoreleasepool {
- // Change superclass at runtime. This allows seamless switching from PST* to UI* at runtime.
- PSTRegisterClasses();
- // add PSUI classes at runtime to make Interface Builder sane
- // (IB doesn't allow adding the PSUICollectionView_ types but doesn't complain on unknown classes)
- // 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).
- Class c;
- if ((c = objc_allocateClassPair(PSUICollectionView_.class, "PSUICollectionView", 0))) objc_registerClassPair(c);
- if ((c = objc_allocateClassPair(PSUICollectionViewCell_.class, "PSUICollectionViewCell", 0))) objc_registerClassPair(c);
- if ((c = objc_allocateClassPair(PSUICollectionReusableView_.class, "PSUICollectionReusableView", 0))) objc_registerClassPair(c);
- if ((c = objc_allocateClassPair(PSUICollectionViewLayout_.class, "PSUICollectionViewLayout", 0))) objc_registerClassPair(c);
- if ((c = objc_allocateClassPair(PSUICollectionViewFlowLayout_.class, "PSUICollectionViewFlowLayout", 0))) objc_registerClassPair(c);
- if ((c = objc_allocateClassPair(PSUICollectionViewLayoutAttributes_.class, "PSUICollectionViewLayoutAttributes", 0)))objc_registerClassPair(c);
- if ((c = objc_allocateClassPair(PSUICollectionViewController_.class, "PSUICollectionViewController", 0))) objc_registerClassPair(c);
- }
- }
- CGFloat PSTSimulatorAnimationDragCoefficient(void) {
- static CGFloat (*UIAnimationDragCoefficient)(void) = NULL;
- #if TARGET_IPHONE_SIMULATOR
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- UIAnimationDragCoefficient = (CGFloat (*)(void))dlsym(RTLD_DEFAULT, "UIAnimationDragCoefficient");
- });
- #endif
- return UIAnimationDragCoefficient ? UIAnimationDragCoefficient() : 1.f;
- }
- // helper to check for ivar layout
- #if 0
- static void PSTPrintIvarsForClass(Class aClass) {
- unsigned int varCount;
- Ivar *vars = class_copyIvarList(aClass, &varCount);
- for (int i = 0; i < varCount; i++) {
- NSLog(@"%s %s", ivar_getTypeEncoding(vars[i]), ivar_getName(vars[i]));
- }
- free(vars);
- }
- __attribute__((constructor)) static void PSTCheckIfIVarLayoutIsEqualSize(void) {
- @autoreleasepool {
- NSLog(@"PSTCollectionView size = %zd, UICollectionView size = %zd", class_getInstanceSize(PSTCollectionView.class),class_getInstanceSize(UICollectionView.class));
- NSLog(@"PSTCollectionViewCell size = %zd, UICollectionViewCell size = %zd", class_getInstanceSize(PSTCollectionViewCell.class),class_getInstanceSize(UICollectionViewCell.class));
- NSLog(@"PSTCollectionViewController size = %zd, UICollectionViewController size = %zd", class_getInstanceSize(PSTCollectionViewController.class),class_getInstanceSize(UICollectionViewController.class));
- NSLog(@"PSTCollectionViewLayout size = %zd, UICollectionViewLayout size = %zd", class_getInstanceSize(PSTCollectionViewLayout.class),class_getInstanceSize(UICollectionViewLayout.class));
- NSLog(@"PSTCollectionViewFlowLayout size = %zd, UICollectionViewFlowLayout size = %zd", class_getInstanceSize(PSTCollectionViewFlowLayout.class),class_getInstanceSize(UICollectionViewFlowLayout.class));
- //PSTPrintIvarsForClass(PSTCollectionViewFlowLayout.class); NSLog(@"\n\n\n");PSTPrintIvarsForClass(UICollectionViewFlowLayout.class);
- }
- }
- #endif
|