PSTCollectionViewLayout.m 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. //
  2. // PSTCollectionViewLayout.m
  3. // PSPDFKit
  4. //
  5. // Copyright (c) 2012-2013 Peter Steinberger. All rights reserved.
  6. //
  7. #import "PSTCollectionView.h"
  8. #import "PSTCollectionViewItemKey.h"
  9. #import "PSTCollectionViewData.h"
  10. #import <objc/runtime.h>
  11. @interface PSTCollectionView ()
  12. - (id)currentUpdate;
  13. - (NSDictionary *)visibleViewsDict;
  14. - (PSTCollectionViewData *)collectionViewData;
  15. - (CGRect)visibleBoundRects; // visibleBounds is flagged as private API (wtf)
  16. @end
  17. @interface PSTCollectionReusableView ()
  18. - (void)setIndexPath:(NSIndexPath *)indexPath;
  19. @end
  20. @interface PSTCollectionViewUpdateItem ()
  21. - (BOOL)isSectionOperation;
  22. @end
  23. @interface PSTCollectionViewLayoutAttributes () {
  24. struct {
  25. unsigned int isCellKind:1;
  26. unsigned int isDecorationView:1;
  27. unsigned int isHidden:1;
  28. }_layoutFlags;
  29. char filler[20]; // [HACK] Our class needs to be larger than Apple's class for the superclass change to work.
  30. }
  31. @property (nonatomic) PSTCollectionViewItemType elementCategory;
  32. @property (nonatomic, copy) NSString *elementKind;
  33. @end
  34. @interface PSTCollectionViewUpdateItem ()
  35. - (NSIndexPath *)indexPath;
  36. @end
  37. @implementation PSTCollectionViewLayoutAttributes
  38. ///////////////////////////////////////////////////////////////////////////////////////////
  39. #pragma mark - Static
  40. + (instancetype)layoutAttributesForCellWithIndexPath:(NSIndexPath *)indexPath {
  41. PSTCollectionViewLayoutAttributes *attributes = [self new];
  42. attributes.elementKind = PSTCollectionElementKindCell;
  43. attributes.elementCategory = PSTCollectionViewItemTypeCell;
  44. attributes.indexPath = indexPath;
  45. return attributes;
  46. }
  47. + (instancetype)layoutAttributesForSupplementaryViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath {
  48. PSTCollectionViewLayoutAttributes *attributes = [self new];
  49. attributes.elementCategory = PSTCollectionViewItemTypeSupplementaryView;
  50. attributes.elementKind = elementKind;
  51. attributes.indexPath = indexPath;
  52. return attributes;
  53. }
  54. + (instancetype)layoutAttributesForDecorationViewOfKind:(NSString *)elementKind withIndexPath:(NSIndexPath *)indexPath {
  55. PSTCollectionViewLayoutAttributes *attributes = [self new];
  56. attributes.elementCategory = PSTCollectionViewItemTypeDecorationView;
  57. attributes.elementKind = elementKind;
  58. attributes.indexPath = indexPath;
  59. return attributes;
  60. }
  61. ///////////////////////////////////////////////////////////////////////////////////////////
  62. #pragma mark - NSObject
  63. - (id)init {
  64. if ((self = [super init])) {
  65. _alpha = 1.f;
  66. _transform3D = CATransform3DIdentity;
  67. }
  68. return self;
  69. }
  70. - (NSUInteger)hash {
  71. return ([_elementKind hash] * 31) + [_indexPath hash];
  72. }
  73. - (BOOL)isEqual:(id)other {
  74. if ([other isKindOfClass:self.class]) {
  75. PSTCollectionViewLayoutAttributes *otherLayoutAttributes = (PSTCollectionViewLayoutAttributes *)other;
  76. if (_elementCategory == otherLayoutAttributes.elementCategory && [_elementKind isEqual:otherLayoutAttributes.elementKind] && [_indexPath isEqual:otherLayoutAttributes.indexPath]) {
  77. return YES;
  78. }
  79. }
  80. return NO;
  81. }
  82. - (NSString *)description {
  83. return [NSString stringWithFormat:@"<%@: %p frame:%@ indexPath:%@ elementKind:%@>", NSStringFromClass(self.class), self, NSStringFromCGRect(self.frame), self.indexPath, self.elementKind];
  84. }
  85. ///////////////////////////////////////////////////////////////////////////////////////////
  86. #pragma mark - Public
  87. - (PSTCollectionViewItemType)representedElementCategory {
  88. return _elementCategory;
  89. }
  90. ///////////////////////////////////////////////////////////////////////////////////////////
  91. #pragma mark - Private
  92. - (NSString *)representedElementKind {
  93. return self.elementKind;
  94. }
  95. - (BOOL)isDecorationView {
  96. return self.representedElementCategory == PSTCollectionViewItemTypeDecorationView;
  97. }
  98. - (BOOL)isSupplementaryView {
  99. return self.representedElementCategory == PSTCollectionViewItemTypeSupplementaryView;
  100. }
  101. - (BOOL)isCell {
  102. return self.representedElementCategory == PSTCollectionViewItemTypeCell;
  103. }
  104. - (void)setSize:(CGSize)size {
  105. _size = size;
  106. _frame = (CGRect){_frame.origin, _size};
  107. }
  108. - (void)setCenter:(CGPoint)center {
  109. _center = center;
  110. _frame = (CGRect){{_center.x - _frame.size.width / 2, _center.y - _frame.size.height / 2}, _frame.size};
  111. }
  112. - (void)setFrame:(CGRect)frame {
  113. _frame = frame;
  114. _size = _frame.size;
  115. _center = (CGPoint){CGRectGetMidX(_frame), CGRectGetMidY(_frame)};
  116. }
  117. ///////////////////////////////////////////////////////////////////////////////////////////
  118. #pragma mark - NSCopying
  119. - (id)copyWithZone:(NSZone *)zone {
  120. PSTCollectionViewLayoutAttributes *layoutAttributes = [self.class new];
  121. layoutAttributes.indexPath = self.indexPath;
  122. layoutAttributes.elementKind = self.elementKind;
  123. layoutAttributes.elementCategory = self.elementCategory;
  124. layoutAttributes.frame = self.frame;
  125. layoutAttributes.center = self.center;
  126. layoutAttributes.size = self.size;
  127. layoutAttributes.transform3D = self.transform3D;
  128. layoutAttributes.alpha = self.alpha;
  129. layoutAttributes.zIndex = self.zIndex;
  130. layoutAttributes.hidden = self.isHidden;
  131. return layoutAttributes;
  132. }
  133. ///////////////////////////////////////////////////////////////////////////////////////////
  134. #pragma mark - PSTCollection/UICollection interoperability
  135. - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
  136. NSMethodSignature *signature = [super methodSignatureForSelector:selector];
  137. if (!signature) {
  138. NSString *selString = NSStringFromSelector(selector);
  139. if ([selString hasPrefix:@"_"]) {
  140. SEL cleanedSelector = NSSelectorFromString([selString substringFromIndex:1]);
  141. signature = [super methodSignatureForSelector:cleanedSelector];
  142. }
  143. }
  144. return signature;
  145. }
  146. - (void)forwardInvocation:(NSInvocation *)invocation {
  147. NSString *selString = NSStringFromSelector([invocation selector]);
  148. if ([selString hasPrefix:@"_"]) {
  149. SEL cleanedSelector = NSSelectorFromString([selString substringFromIndex:1]);
  150. if ([self respondsToSelector:cleanedSelector]) {
  151. invocation.selector = cleanedSelector;
  152. [invocation invokeWithTarget:self];
  153. }
  154. }else {
  155. [super forwardInvocation:invocation];
  156. }
  157. }
  158. @end
  159. @interface PSTCollectionViewLayout () {
  160. __unsafe_unretained PSTCollectionView *_collectionView;
  161. CGSize _collectionViewBoundsSize;
  162. NSMutableDictionary *_initialAnimationLayoutAttributesDict;
  163. NSMutableDictionary *_finalAnimationLayoutAttributesDict;
  164. NSMutableIndexSet *_deletedSectionsSet;
  165. NSMutableIndexSet *_insertedSectionsSet;
  166. NSMutableDictionary *_decorationViewClassDict;
  167. NSMutableDictionary *_decorationViewNibDict;
  168. NSMutableDictionary *_decorationViewExternalObjectsTables;
  169. char filler[200]; // [HACK] Our class needs to be larger than Apple's class for the superclass change to work.
  170. }
  171. @property (nonatomic, unsafe_unretained) PSTCollectionView *collectionView;
  172. @property (nonatomic, copy, readonly) NSDictionary *decorationViewClassDict;
  173. @property (nonatomic, copy, readonly) NSDictionary *decorationViewNibDict;
  174. @property (nonatomic, copy, readonly) NSDictionary *decorationViewExternalObjectsTables;
  175. @end
  176. @implementation PSTCollectionViewLayout
  177. ///////////////////////////////////////////////////////////////////////////////////////////
  178. #pragma mark - NSObject
  179. - (id)init {
  180. if ((self = [super init])) {
  181. _decorationViewClassDict = [NSMutableDictionary new];
  182. _decorationViewNibDict = [NSMutableDictionary new];
  183. _decorationViewExternalObjectsTables = [NSMutableDictionary new];
  184. _initialAnimationLayoutAttributesDict = [NSMutableDictionary new];
  185. _finalAnimationLayoutAttributesDict = [NSMutableDictionary new];
  186. _insertedSectionsSet = [NSMutableIndexSet new];
  187. _deletedSectionsSet = [NSMutableIndexSet new];
  188. }
  189. return self;
  190. }
  191. - (void)awakeFromNib {
  192. [super awakeFromNib];
  193. }
  194. - (void)setCollectionView:(PSTCollectionView *)collectionView {
  195. if (collectionView != _collectionView) {
  196. _collectionView = collectionView;
  197. }
  198. }
  199. ///////////////////////////////////////////////////////////////////////////////////////////
  200. #pragma mark - Invalidating the Layout
  201. - (void)invalidateLayout {
  202. [[_collectionView collectionViewData] invalidate];
  203. [_collectionView setNeedsLayout];
  204. }
  205. - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
  206. // not sure about his..
  207. if ((self.collectionView.bounds.size.width != newBounds.size.width) || (self.collectionView.bounds.size.height != newBounds.size.height)) {
  208. return YES;
  209. }
  210. return NO;
  211. }
  212. ///////////////////////////////////////////////////////////////////////////////////////////
  213. #pragma mark - Providing Layout Attributes
  214. + (Class)layoutAttributesClass {
  215. return PSTCollectionViewLayoutAttributes.class;
  216. }
  217. - (void)prepareLayout {
  218. }
  219. - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
  220. return nil;
  221. }
  222. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
  223. return nil;
  224. }
  225. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
  226. return nil;
  227. }
  228. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
  229. return nil;
  230. }
  231. // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior
  232. - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
  233. return proposedContentOffset;
  234. }
  235. - (CGSize)collectionViewContentSize {
  236. return CGSizeZero;
  237. }
  238. ///////////////////////////////////////////////////////////////////////////////////////////
  239. #pragma mark - Responding to Collection View Updates
  240. - (void)prepareForCollectionViewUpdates:(NSArray *)updateItems {
  241. NSDictionary *update = [_collectionView currentUpdate];
  242. for (PSTCollectionReusableView *view in [[_collectionView visibleViewsDict] objectEnumerator]) {
  243. PSTCollectionViewLayoutAttributes *attr = [view.layoutAttributes copy];
  244. if (attr) {
  245. if (attr.isCell) {
  246. NSInteger index = [update[@"oldModel"] globalIndexForItemAtIndexPath:[attr indexPath]];
  247. if (index != NSNotFound) {
  248. [attr setIndexPath:[attr indexPath]];
  249. }
  250. }
  251. _initialAnimationLayoutAttributesDict[[PSTCollectionViewItemKey collectionItemKeyForLayoutAttributes:attr]] = attr;
  252. }
  253. }
  254. PSTCollectionViewData *collectionViewData = [_collectionView collectionViewData];
  255. CGRect bounds = [_collectionView visibleBoundRects];
  256. for (PSTCollectionViewLayoutAttributes *attr in [collectionViewData layoutAttributesForElementsInRect:bounds]) {
  257. if (attr.isCell) {
  258. NSInteger index = [collectionViewData globalIndexForItemAtIndexPath:attr.indexPath];
  259. index = [update[@"newToOldIndexMap"][index] intValue];
  260. if (index != NSNotFound) {
  261. PSTCollectionViewLayoutAttributes *finalAttrs = [attr copy];
  262. [finalAttrs setIndexPath:[update[@"oldModel"] indexPathForItemAtGlobalIndex:index]];
  263. [finalAttrs setAlpha:0];
  264. _finalAnimationLayoutAttributesDict[[PSTCollectionViewItemKey collectionItemKeyForLayoutAttributes:finalAttrs]] = finalAttrs;
  265. }
  266. }
  267. }
  268. for (PSTCollectionViewUpdateItem *updateItem in updateItems) {
  269. PSTCollectionUpdateAction action = updateItem.updateAction;
  270. if ([updateItem isSectionOperation]) {
  271. if (action == PSTCollectionUpdateActionReload) {
  272. [_deletedSectionsSet addIndex:[[updateItem indexPathBeforeUpdate] section]];
  273. [_insertedSectionsSet addIndex:[updateItem indexPathAfterUpdate].section];
  274. }
  275. else {
  276. NSMutableIndexSet *indexSet = action == PSTCollectionUpdateActionInsert ? _insertedSectionsSet : _deletedSectionsSet;
  277. [indexSet addIndex:[updateItem indexPath].section];
  278. }
  279. }
  280. else {
  281. if (action == PSTCollectionUpdateActionDelete) {
  282. PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:
  283. [updateItem indexPathBeforeUpdate]];
  284. PSTCollectionViewLayoutAttributes *attrs = [_finalAnimationLayoutAttributesDict[key] copy];
  285. if (attrs) {
  286. [attrs setAlpha:0];
  287. _finalAnimationLayoutAttributesDict[key] = attrs;
  288. }
  289. }
  290. else if (action == PSTCollectionUpdateActionReload || action == PSTCollectionUpdateActionInsert) {
  291. PSTCollectionViewItemKey *key = [PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:
  292. [updateItem indexPathAfterUpdate]];
  293. PSTCollectionViewLayoutAttributes *attrs = [_initialAnimationLayoutAttributesDict[key] copy];
  294. if (attrs) {
  295. [attrs setAlpha:0];
  296. _initialAnimationLayoutAttributesDict[key] = attrs;
  297. }
  298. }
  299. }
  300. }
  301. }
  302. - (PSTCollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
  303. PSTCollectionViewLayoutAttributes *attrs = _initialAnimationLayoutAttributesDict[[PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:itemIndexPath]];
  304. if ([_insertedSectionsSet containsIndex:[itemIndexPath section]]) {
  305. attrs = [attrs copy];
  306. [attrs setAlpha:0];
  307. }
  308. return attrs;
  309. }
  310. - (PSTCollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath {
  311. PSTCollectionViewLayoutAttributes *attrs = _finalAnimationLayoutAttributesDict[[PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:itemIndexPath]];
  312. if ([_deletedSectionsSet containsIndex:[itemIndexPath section]]) {
  313. attrs = [attrs copy];
  314. [attrs setAlpha:0];
  315. }
  316. return attrs;
  317. }
  318. - (PSTCollectionViewLayoutAttributes *)initialLayoutAttributesForInsertedSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath {
  319. PSTCollectionViewLayoutAttributes *attrs = _initialAnimationLayoutAttributesDict[[PSTCollectionViewItemKey collectionItemKeyForCellWithIndexPath:elementIndexPath]];
  320. if ([_insertedSectionsSet containsIndex:[elementIndexPath section]]) {
  321. attrs = [attrs copy];
  322. [attrs setAlpha:0];
  323. }
  324. return attrs;
  325. }
  326. - (PSTCollectionViewLayoutAttributes *)finalLayoutAttributesForDeletedSupplementaryElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)elementIndexPath {
  327. return nil;
  328. }
  329. - (void)finalizeCollectionViewUpdates {
  330. [_initialAnimationLayoutAttributesDict removeAllObjects];
  331. [_finalAnimationLayoutAttributesDict removeAllObjects];
  332. [_deletedSectionsSet removeAllIndexes];
  333. [_insertedSectionsSet removeAllIndexes];
  334. }
  335. ///////////////////////////////////////////////////////////////////////////////////////////
  336. #pragma mark - Registering Decoration Views
  337. - (void)registerClass:(Class)viewClass forDecorationViewOfKind:(NSString *)kind {
  338. _decorationViewClassDict[kind] = viewClass;
  339. }
  340. - (void)registerNib:(UINib *)nib forDecorationViewOfKind:(NSString *)kind {
  341. _decorationViewNibDict[kind] = nib;
  342. }
  343. ///////////////////////////////////////////////////////////////////////////////////////////
  344. #pragma mark - Private
  345. - (void)setCollectionViewBoundsSize:(CGSize)size {
  346. _collectionViewBoundsSize = size;
  347. }
  348. ///////////////////////////////////////////////////////////////////////////////////////////
  349. #pragma mark - NSCoding
  350. - (id)initWithCoder:(NSCoder *)coder {
  351. if ((self = [self init])) {
  352. }
  353. return self;
  354. }
  355. - (void)encodeWithCoder:(NSCoder *)coder {}
  356. ///////////////////////////////////////////////////////////////////////////////////////////
  357. #pragma mark - PSTCollection/UICollection interoperability
  358. #ifdef kPSUIInteroperabilityEnabled
  359. #import <objc/runtime.h>
  360. #import <objc/message.h>
  361. - (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
  362. NSMethodSignature *sig = [super methodSignatureForSelector:selector];
  363. if(!sig) {
  364. NSString *selString = NSStringFromSelector(selector);
  365. if ([selString hasPrefix:@"_"]) {
  366. SEL cleanedSelector = NSSelectorFromString([selString substringFromIndex:1]);
  367. sig = [super methodSignatureForSelector:cleanedSelector];
  368. }
  369. }
  370. return sig;
  371. }
  372. - (void)forwardInvocation:(NSInvocation *)inv {
  373. NSString *selString = NSStringFromSelector([inv selector]);
  374. if ([selString hasPrefix:@"_"]) {
  375. SEL cleanedSelector = NSSelectorFromString([selString substringFromIndex:1]);
  376. if ([self respondsToSelector:cleanedSelector]) {
  377. // dynamically add method for faster resolving
  378. Method newMethod = class_getInstanceMethod(self.class, [inv selector]);
  379. IMP underscoreIMP = imp_implementationWithBlock(^(id _self) {
  380. return objc_msgSend(_self, cleanedSelector);
  381. });
  382. class_addMethod(self.class, [inv selector], underscoreIMP, method_getTypeEncoding(newMethod));
  383. // invoke now
  384. inv.selector = cleanedSelector;
  385. [inv invokeWithTarget:self];
  386. }
  387. }else {
  388. [super forwardInvocation:inv];
  389. }
  390. }
  391. #endif
  392. @end