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

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

https://gitlab.com/lisit1003/TTiOSClient
Objective C | 420 lines | 327 code | 65 blank | 28 comment | 60 complexity | e5ecc94680502b4c1ad4b539e7e68356 MD5 | raw file
  1. //
  2. // PSTCollectionViewFlowLayout.m
  3. // PSPDFKit
  4. //
  5. // Copyright (c) 2012-2013 Peter Steinberger. All rights reserved.
  6. //
  7. #import "PSTCollectionViewFlowLayout.h"
  8. #import "PSTCollectionView.h"
  9. #import "PSTGridLayoutItem.h"
  10. #import "PSTGridLayoutInfo.h"
  11. #import "PSTGridLayoutRow.h"
  12. #import "PSTGridLayoutSection.h"
  13. #import <objc/runtime.h>
  14. NSString *const PSTCollectionElementKindSectionHeader = @"UICollectionElementKindSectionHeader";
  15. NSString *const PSTCollectionElementKindSectionFooter = @"UICollectionElementKindSectionFooter";
  16. // this is not exposed in UICollectionViewFlowLayout
  17. NSString *const PSTFlowLayoutCommonRowHorizontalAlignmentKey = @"UIFlowLayoutCommonRowHorizontalAlignmentKey";
  18. NSString *const PSTFlowLayoutLastRowHorizontalAlignmentKey = @"UIFlowLayoutLastRowHorizontalAlignmentKey";
  19. NSString *const PSTFlowLayoutRowVerticalAlignmentKey = @"UIFlowLayoutRowVerticalAlignmentKey";
  20. @implementation PSTCollectionViewFlowLayout {
  21. // class needs to have same iVar layout as UICollectionViewLayout
  22. struct {
  23. unsigned int delegateSizeForItem : 1;
  24. unsigned int delegateReferenceSizeForHeader : 1;
  25. unsigned int delegateReferenceSizeForFooter : 1;
  26. unsigned int delegateInsetForSection : 1;
  27. unsigned int delegateInteritemSpacingForSection : 1;
  28. unsigned int delegateLineSpacingForSection : 1;
  29. unsigned int delegateAlignmentOptions : 1;
  30. unsigned int keepDelegateInfoWhileInvalidating : 1;
  31. unsigned int keepAllDataWhileInvalidating : 1;
  32. unsigned int layoutDataIsValid : 1;
  33. unsigned int delegateInfoIsValid : 1;
  34. }_gridLayoutFlags;
  35. CGFloat _interitemSpacing;
  36. CGFloat _lineSpacing;
  37. CGSize _itemSize;
  38. CGSize _headerReferenceSize;
  39. CGSize _footerReferenceSize;
  40. UIEdgeInsets _sectionInset;
  41. PSTGridLayoutInfo *_data;
  42. CGSize _currentLayoutSize;
  43. NSMutableDictionary *_insertedItemsAttributesDict;
  44. NSMutableDictionary *_insertedSectionHeadersAttributesDict;
  45. NSMutableDictionary *_insertedSectionFootersAttributesDict;
  46. NSMutableDictionary *_deletedItemsAttributesDict;
  47. NSMutableDictionary *_deletedSectionHeadersAttributesDict;
  48. NSMutableDictionary *_deletedSectionFootersAttributesDict;
  49. PSTCollectionViewScrollDirection _scrollDirection;
  50. NSDictionary *_rowAlignmentsOptionsDictionary;
  51. CGRect _visibleBounds;
  52. char filler[200]; // [HACK] Our class needs to be larger than Apple's class for the superclass change to work.
  53. }
  54. @synthesize rowAlignmentOptions = _rowAlignmentsOptionsDictionary;
  55. @synthesize minimumLineSpacing = _lineSpacing;
  56. @synthesize minimumInteritemSpacing = _interitemSpacing;
  57. ///////////////////////////////////////////////////////////////////////////////////////////
  58. #pragma mark - NSObject
  59. - (void)commonInit {
  60. _itemSize = CGSizeMake(50.f, 50.f);
  61. _lineSpacing = 10.f;
  62. _interitemSpacing = 10.f;
  63. _sectionInset = UIEdgeInsetsZero;
  64. _scrollDirection = PSTCollectionViewScrollDirectionVertical;
  65. _headerReferenceSize = CGSizeZero;
  66. _footerReferenceSize = CGSizeZero;
  67. }
  68. - (id)init {
  69. if ((self = [super init])) {
  70. [self commonInit];
  71. // set default values for row alignment.
  72. _rowAlignmentsOptionsDictionary = @{
  73. PSTFlowLayoutCommonRowHorizontalAlignmentKey : @(PSTFlowLayoutHorizontalAlignmentJustify),
  74. PSTFlowLayoutLastRowHorizontalAlignmentKey : @(PSTFlowLayoutHorizontalAlignmentJustify),
  75. // TODO: those values are some enum. find out what that is.
  76. PSTFlowLayoutRowVerticalAlignmentKey : @(1),
  77. };
  78. }
  79. return self;
  80. }
  81. - (id)initWithCoder:(NSCoder *)decoder {
  82. if ((self = [super initWithCoder:decoder])) {
  83. [self commonInit];
  84. // Some properties are not set if they're default (like minimumInteritemSpacing == 10)
  85. if ([decoder containsValueForKey:@"UIItemSize"])
  86. self.itemSize = [decoder decodeCGSizeForKey:@"UIItemSize"];
  87. if ([decoder containsValueForKey:@"UIInteritemSpacing"])
  88. self.minimumInteritemSpacing = [decoder decodeFloatForKey:@"UIInteritemSpacing"];
  89. if ([decoder containsValueForKey:@"UILineSpacing"])
  90. self.minimumLineSpacing = [decoder decodeFloatForKey:@"UILineSpacing"];
  91. if ([decoder containsValueForKey:@"UIFooterReferenceSize"])
  92. self.footerReferenceSize = [decoder decodeCGSizeForKey:@"UIFooterReferenceSize"];
  93. if ([decoder containsValueForKey:@"UIHeaderReferenceSize"])
  94. self.headerReferenceSize = [decoder decodeCGSizeForKey:@"UIHeaderReferenceSize"];
  95. if ([decoder containsValueForKey:@"UISectionInset"])
  96. self.sectionInset = [decoder decodeUIEdgeInsetsForKey:@"UISectionInset"];
  97. if ([decoder containsValueForKey:@"UIScrollDirection"])
  98. self.scrollDirection = [decoder decodeIntegerForKey:@"UIScrollDirection"];
  99. }
  100. return self;
  101. }
  102. - (void)encodeWithCoder:(NSCoder *)coder {
  103. [super encodeWithCoder:coder];
  104. [coder encodeCGSize:self.itemSize forKey:@"UIItemSize"];
  105. [coder encodeFloat:(float)self.minimumInteritemSpacing forKey:@"UIInteritemSpacing"];
  106. [coder encodeFloat:(float)self.minimumLineSpacing forKey:@"UILineSpacing"];
  107. [coder encodeCGSize:self.footerReferenceSize forKey:@"UIFooterReferenceSize"];
  108. [coder encodeCGSize:self.headerReferenceSize forKey:@"UIHeaderReferenceSize"];
  109. [coder encodeUIEdgeInsets:self.sectionInset forKey:@"UISectionInset"];
  110. [coder encodeInteger:self.scrollDirection forKey:@"UIScrollDirection"];
  111. }
  112. ///////////////////////////////////////////////////////////////////////////////////////////
  113. #pragma mark - PSTCollectionViewLayout
  114. static char kPSTCachedItemRectsKey;
  115. - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
  116. // Apple calls _layoutAttributesForItemsInRect
  117. if (!_data) [self prepareLayout];
  118. NSMutableArray *layoutAttributesArray = [NSMutableArray array];
  119. for (PSTGridLayoutSection *section in _data.sections) {
  120. if (CGRectIntersectsRect(section.frame, rect)) {
  121. // if we have fixed size, calculate item frames only once.
  122. // this also uses the default PSTFlowLayoutCommonRowHorizontalAlignmentKey alignment
  123. // for the last row. (we want this effect!)
  124. NSMutableDictionary *rectCache = objc_getAssociatedObject(self, &kPSTCachedItemRectsKey);
  125. NSUInteger sectionIndex = [_data.sections indexOfObjectIdenticalTo:section];
  126. CGRect normalizedHeaderFrame = section.headerFrame;
  127. normalizedHeaderFrame.origin.x += section.frame.origin.x;
  128. normalizedHeaderFrame.origin.y += section.frame.origin.y;
  129. if (!CGRectIsEmpty(normalizedHeaderFrame) && CGRectIntersectsRect(normalizedHeaderFrame, rect)) {
  130. PSTCollectionViewLayoutAttributes *layoutAttributes = [[self.class layoutAttributesClass] layoutAttributesForSupplementaryViewOfKind:PSTCollectionElementKindSectionHeader withIndexPath:[NSIndexPath indexPathForItem:0 inSection:sectionIndex]];
  131. layoutAttributes.frame = normalizedHeaderFrame;
  132. [layoutAttributesArray addObject:layoutAttributes];
  133. }
  134. NSArray *itemRects = rectCache[@(sectionIndex)];
  135. if (!itemRects && section.fixedItemSize && section.rows.count) {
  136. itemRects = [(section.rows)[0] itemRects];
  137. if (itemRects) rectCache[@(sectionIndex)] = itemRects;
  138. }
  139. for (PSTGridLayoutRow *row in section.rows) {
  140. CGRect normalizedRowFrame = row.rowFrame;
  141. normalizedRowFrame.origin.x += section.frame.origin.x;
  142. normalizedRowFrame.origin.y += section.frame.origin.y;
  143. if (CGRectIntersectsRect(normalizedRowFrame, rect)) {
  144. // TODO be more fine-grained for items
  145. for (NSInteger itemIndex = 0; itemIndex < row.itemCount; itemIndex++) {
  146. PSTCollectionViewLayoutAttributes *layoutAttributes;
  147. NSUInteger sectionItemIndex;
  148. CGRect itemFrame;
  149. if (row.fixedItemSize) {
  150. itemFrame = [itemRects[itemIndex] CGRectValue];
  151. sectionItemIndex = row.index * section.itemsByRowCount + itemIndex;
  152. }else {
  153. PSTGridLayoutItem *item = row.items[itemIndex];
  154. sectionItemIndex = [section.items indexOfObjectIdenticalTo:item];
  155. itemFrame = item.itemFrame;
  156. }
  157. CGRect normalisedItemFrame = CGRectMake(normalizedRowFrame.origin.x + itemFrame.origin.x, normalizedRowFrame.origin.y + itemFrame.origin.y, itemFrame.size.width, itemFrame.size.height);
  158. if (CGRectIntersectsRect(normalisedItemFrame, rect)) {
  159. layoutAttributes = [[self.class layoutAttributesClass] layoutAttributesForCellWithIndexPath:[NSIndexPath indexPathForItem:sectionItemIndex inSection:sectionIndex]];
  160. layoutAttributes.frame = normalisedItemFrame;
  161. [layoutAttributesArray addObject:layoutAttributes];
  162. }
  163. }
  164. }
  165. }
  166. CGRect normalizedFooterFrame = section.footerFrame;
  167. normalizedFooterFrame.origin.x += section.frame.origin.x;
  168. normalizedFooterFrame.origin.y += section.frame.origin.y;
  169. if (!CGRectIsEmpty(normalizedFooterFrame) && CGRectIntersectsRect(normalizedFooterFrame, rect)) {
  170. PSTCollectionViewLayoutAttributes *layoutAttributes = [[self.class layoutAttributesClass] layoutAttributesForSupplementaryViewOfKind:PSTCollectionElementKindSectionFooter withIndexPath:[NSIndexPath indexPathForItem:0 inSection:sectionIndex]];
  171. layoutAttributes.frame = normalizedFooterFrame;
  172. [layoutAttributesArray addObject:layoutAttributes];
  173. }
  174. }
  175. }
  176. return layoutAttributesArray;
  177. }
  178. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
  179. if (!_data) [self prepareLayout];
  180. PSTGridLayoutSection *section = _data.sections[indexPath.section];
  181. PSTGridLayoutRow *row = nil;
  182. CGRect itemFrame = CGRectZero;
  183. if (section.fixedItemSize && section.itemsByRowCount > 0 && indexPath.item / section.itemsByRowCount < (NSInteger)section.rows.count) {
  184. row = section.rows[indexPath.item / section.itemsByRowCount];
  185. NSUInteger itemIndex = indexPath.item % section.itemsByRowCount;
  186. NSArray *itemRects = [row itemRects];
  187. itemFrame = [itemRects[itemIndex] CGRectValue];
  188. }else if (indexPath.item < (NSInteger)section.items.count) {
  189. PSTGridLayoutItem *item = section.items[indexPath.item];
  190. row = item.rowObject;
  191. itemFrame = item.itemFrame;
  192. }
  193. PSTCollectionViewLayoutAttributes *layoutAttributes = [[self.class layoutAttributesClass] layoutAttributesForCellWithIndexPath:indexPath];
  194. // calculate item rect
  195. CGRect normalizedRowFrame = row.rowFrame;
  196. normalizedRowFrame.origin.x += section.frame.origin.x;
  197. normalizedRowFrame.origin.y += section.frame.origin.y;
  198. layoutAttributes.frame = CGRectMake(normalizedRowFrame.origin.x + itemFrame.origin.x, normalizedRowFrame.origin.y + itemFrame.origin.y, itemFrame.size.width, itemFrame.size.height);
  199. return layoutAttributes;
  200. }
  201. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath {
  202. if (!_data) [self prepareLayout];
  203. NSUInteger sectionIndex = indexPath.section;
  204. PSTCollectionViewLayoutAttributes *layoutAttributes = nil;
  205. if (sectionIndex < _data.sections.count) {
  206. PSTGridLayoutSection *section = _data.sections[sectionIndex];
  207. CGRect normalizedFrame = CGRectZero;
  208. if ([kind isEqualToString:PSTCollectionElementKindSectionHeader]) {
  209. normalizedFrame = section.headerFrame;
  210. }
  211. else if ([kind isEqualToString:PSTCollectionElementKindSectionFooter]) {
  212. normalizedFrame = section.footerFrame;
  213. }
  214. if (!CGRectIsEmpty(normalizedFrame)) {
  215. normalizedFrame.origin.x += section.frame.origin.x;
  216. normalizedFrame.origin.y += section.frame.origin.y;
  217. layoutAttributes = [[self.class layoutAttributesClass] layoutAttributesForSupplementaryViewOfKind:kind withIndexPath:[NSIndexPath indexPathForItem:0 inSection:sectionIndex]];
  218. layoutAttributes.frame = normalizedFrame;
  219. }
  220. }
  221. return layoutAttributes;
  222. }
  223. - (PSTCollectionViewLayoutAttributes *)layoutAttributesForDecorationViewWithReuseIdentifier:(NSString *)identifier atIndexPath:(NSIndexPath *)indexPath {
  224. return nil;
  225. }
  226. - (CGSize)collectionViewContentSize {
  227. if (!_data) [self prepareLayout];
  228. return _data.contentSize;
  229. }
  230. - (void)setSectionInset:(UIEdgeInsets)sectionInset {
  231. if (!UIEdgeInsetsEqualToEdgeInsets(sectionInset, _sectionInset)) {
  232. _sectionInset = sectionInset;
  233. [self invalidateLayout];
  234. }
  235. }
  236. ///////////////////////////////////////////////////////////////////////////////////////////
  237. #pragma mark - Invalidating the Layout
  238. - (void)invalidateLayout {
  239. [super invalidateLayout];
  240. objc_setAssociatedObject(self, &kPSTCachedItemRectsKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  241. _data = nil;
  242. }
  243. - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
  244. // we need to recalculate on width changes
  245. if ((_visibleBounds.size.width != newBounds.size.width && self.scrollDirection == PSTCollectionViewScrollDirectionVertical) || (_visibleBounds.size.height != newBounds.size.height && self.scrollDirection == PSTCollectionViewScrollDirectionHorizontal)) {
  246. _visibleBounds = self.collectionView.bounds;
  247. return YES;
  248. }
  249. return NO;
  250. }
  251. // return a point at which to rest after scrolling - for layouts that want snap-to-point scrolling behavior
  252. - (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity {
  253. return proposedContentOffset;
  254. }
  255. - (void)prepareLayout {
  256. // custom ivars
  257. objc_setAssociatedObject(self, &kPSTCachedItemRectsKey, [NSMutableDictionary dictionary], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  258. _data = [PSTGridLayoutInfo new]; // clear old layout data
  259. _data.horizontal = self.scrollDirection == PSTCollectionViewScrollDirectionHorizontal;
  260. _visibleBounds = self.collectionView.bounds;
  261. CGSize collectionViewSize = _visibleBounds.size;
  262. _data.dimension = _data.horizontal ? collectionViewSize.height : collectionViewSize.width;
  263. _data.rowAlignmentOptions = _rowAlignmentsOptionsDictionary;
  264. [self fetchItemsInfo];
  265. }
  266. ///////////////////////////////////////////////////////////////////////////////////////////
  267. #pragma mark - Private
  268. - (void)fetchItemsInfo {
  269. [self getSizingInfos];
  270. [self updateItemsLayout];
  271. }
  272. // get size of all items (if delegate is implemented)
  273. - (void)getSizingInfos {
  274. NSAssert(_data.sections.count == 0, @"Grid layout is already populated?");
  275. id<PSTCollectionViewDelegateFlowLayout> flowDataSource = (id<PSTCollectionViewDelegateFlowLayout>)self.collectionView.delegate;
  276. BOOL implementsSizeDelegate = [flowDataSource respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)];
  277. BOOL implementsHeaderReferenceDelegate = [flowDataSource respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)];
  278. BOOL implementsFooterReferenceDelegate = [flowDataSource respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)];
  279. NSUInteger numberOfSections = [self.collectionView numberOfSections];
  280. for (NSUInteger section = 0; section < numberOfSections; section++) {
  281. PSTGridLayoutSection *layoutSection = [_data addSection];
  282. layoutSection.verticalInterstice = _data.horizontal ? self.minimumInteritemSpacing : self.minimumLineSpacing;
  283. layoutSection.horizontalInterstice = !_data.horizontal ? self.minimumInteritemSpacing : self.minimumLineSpacing;
  284. if ([flowDataSource respondsToSelector:@selector(collectionView:layout:insetForSectionAtIndex:)]) {
  285. layoutSection.sectionMargins = [flowDataSource collectionView:self.collectionView layout:self insetForSectionAtIndex:section];
  286. }else {
  287. layoutSection.sectionMargins = self.sectionInset;
  288. }
  289. if ([flowDataSource respondsToSelector:@selector(collectionView:layout:minimumLineSpacingForSectionAtIndex:)]) {
  290. CGFloat minimumLineSpacing = [flowDataSource collectionView:self.collectionView layout:self minimumLineSpacingForSectionAtIndex:section];
  291. if (_data.horizontal) {
  292. layoutSection.horizontalInterstice = minimumLineSpacing;
  293. }else {
  294. layoutSection.verticalInterstice = minimumLineSpacing;
  295. }
  296. }
  297. if ([flowDataSource respondsToSelector:@selector(collectionView:layout:minimumInteritemSpacingForSectionAtIndex:)]) {
  298. CGFloat minimumInterimSpacing = [flowDataSource collectionView:self.collectionView layout:self minimumInteritemSpacingForSectionAtIndex:section];
  299. if (_data.horizontal) {
  300. layoutSection.verticalInterstice = minimumInterimSpacing;
  301. }else {
  302. layoutSection.horizontalInterstice = minimumInterimSpacing;
  303. }
  304. }
  305. CGSize headerReferenceSize;
  306. if (implementsHeaderReferenceDelegate) {
  307. headerReferenceSize = [flowDataSource collectionView:self.collectionView layout:self referenceSizeForHeaderInSection:section];
  308. }else {
  309. headerReferenceSize = self.headerReferenceSize;
  310. }
  311. layoutSection.headerDimension = _data.horizontal ? headerReferenceSize.width : headerReferenceSize.height;
  312. CGSize footerReferenceSize;
  313. if (implementsFooterReferenceDelegate) {
  314. footerReferenceSize = [flowDataSource collectionView:self.collectionView layout:self referenceSizeForFooterInSection:section];
  315. }else {
  316. footerReferenceSize = self.footerReferenceSize;
  317. }
  318. layoutSection.footerDimension = _data.horizontal ? footerReferenceSize.width : footerReferenceSize.height;
  319. NSUInteger numberOfItems = [self.collectionView numberOfItemsInSection:section];
  320. // if delegate implements size delegate, query it for all items
  321. if (implementsSizeDelegate) {
  322. for (NSUInteger item = 0; item < numberOfItems; item++) {
  323. NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:section];
  324. CGSize itemSize = implementsSizeDelegate ? [flowDataSource collectionView:self.collectionView layout:self sizeForItemAtIndexPath:indexPath] : self.itemSize;
  325. PSTGridLayoutItem *layoutItem = [layoutSection addItem];
  326. layoutItem.itemFrame = (CGRect){.size=itemSize};
  327. }
  328. // if not, go the fast path
  329. }else {
  330. layoutSection.fixedItemSize = YES;
  331. layoutSection.itemSize = self.itemSize;
  332. layoutSection.itemsCount = numberOfItems;
  333. }
  334. }
  335. }
  336. - (void)updateItemsLayout {
  337. CGSize contentSize = CGSizeZero;
  338. for (PSTGridLayoutSection *section in _data.sections) {
  339. [section computeLayout];
  340. // update section offset to make frame absolute (section only calculates relative)
  341. CGRect sectionFrame = section.frame;
  342. if (_data.horizontal) {
  343. sectionFrame.origin.x += contentSize.width;
  344. contentSize.width += section.frame.size.width + section.frame.origin.x;
  345. contentSize.height = fmax(contentSize.height, sectionFrame.size.height + section.frame.origin.y + section.sectionMargins.top + section.sectionMargins.bottom);
  346. }else {
  347. sectionFrame.origin.y += contentSize.height;
  348. contentSize.height += sectionFrame.size.height + section.frame.origin.y;
  349. contentSize.width = fmax(contentSize.width, sectionFrame.size.width + section.frame.origin.x + section.sectionMargins.left + section.sectionMargins.right);
  350. }
  351. section.frame = sectionFrame;
  352. }
  353. _data.contentSize = contentSize;
  354. }
  355. @end