PSTGridLayoutRow.m 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. //
  2. // PSTGridLayoutRow.m
  3. // PSPDFKit
  4. //
  5. // Copyright (c) 2012-2013 Peter Steinberger. All rights reserved.
  6. //
  7. #import "PSTCollectionView.h"
  8. #import "PSTGridLayoutRow.h"
  9. #import "PSTGridLayoutSection.h"
  10. #import "PSTGridLayoutItem.h"
  11. #import "PSTGridLayoutInfo.h"
  12. @interface PSTGridLayoutRow () {
  13. NSMutableArray *_items;
  14. BOOL _isValid;
  15. int _verticalAlignement;
  16. int _horizontalAlignement;
  17. }
  18. @property (nonatomic, strong) NSArray *items;
  19. @end
  20. @implementation PSTGridLayoutRow
  21. ///////////////////////////////////////////////////////////////////////////////////////////
  22. #pragma mark - NSObject
  23. - (id)init {
  24. if ((self = [super init])) {
  25. _items = [NSMutableArray new];
  26. }
  27. return self;
  28. }
  29. - (NSString *)description {
  30. return [NSString stringWithFormat:@"<%@: %p frame:%@ index:%ld items:%@>", NSStringFromClass(self.class), self, NSStringFromCGRect(self.rowFrame), (long)self.index, self.items];
  31. }
  32. ///////////////////////////////////////////////////////////////////////////////////////////
  33. #pragma mark - Public
  34. - (void)invalidate {
  35. _isValid = NO;
  36. _rowSize = CGSizeZero;
  37. _rowFrame = CGRectZero;
  38. }
  39. - (NSArray *)itemRects {
  40. return [self layoutRowAndGenerateRectArray:YES];
  41. }
  42. - (void)layoutRow {
  43. [self layoutRowAndGenerateRectArray:NO];
  44. }
  45. - (NSArray *)layoutRowAndGenerateRectArray:(BOOL)generateRectArray {
  46. NSMutableArray *rects = generateRectArray ? [NSMutableArray array] : nil;
  47. if (!_isValid || generateRectArray) {
  48. // properties for aligning
  49. BOOL isHorizontal = self.section.layoutInfo.horizontal;
  50. BOOL isLastRow = self.section.indexOfImcompleteRow == self.index;
  51. PSTFlowLayoutHorizontalAlignment horizontalAlignment = [self.section.rowAlignmentOptions[isLastRow ? PSTFlowLayoutLastRowHorizontalAlignmentKey : PSTFlowLayoutCommonRowHorizontalAlignmentKey] integerValue];
  52. // calculate space that's left over if we would align it from left to right.
  53. CGFloat leftOverSpace = self.section.layoutInfo.dimension;
  54. if (isHorizontal) {
  55. leftOverSpace -= self.section.sectionMargins.top + self.section.sectionMargins.bottom;
  56. }else {
  57. leftOverSpace -= self.section.sectionMargins.left + self.section.sectionMargins.right;
  58. }
  59. // calculate the space that we have left after counting all items.
  60. // UICollectionView is smart and lays out items like they would have been placed on a full row
  61. // So we need to calculate the "usedItemCount" with using the last item as a reference size.
  62. // This allows us to correctly justify-place the items in the grid.
  63. NSInteger usedItemCount = 0;
  64. NSInteger itemIndex = 0;
  65. CGFloat spacing = isHorizontal ? self.section.verticalInterstice : self.section.horizontalInterstice;
  66. // the last row should justify as if it is filled with more (invisible) items so that the whole
  67. // UICollectionView feels more like a grid than a random line of blocks
  68. while (itemIndex < self.itemCount || isLastRow) {
  69. CGFloat nextItemSize;
  70. // first we need to find the size (width/height) of the next item to fit
  71. if (!self.fixedItemSize) {
  72. PSTGridLayoutItem *item = self.items[(NSUInteger)MIN(itemIndex, self.itemCount - 1)];
  73. nextItemSize = isHorizontal ? item.itemFrame.size.height : item.itemFrame.size.width;
  74. }else {
  75. nextItemSize = isHorizontal ? self.section.itemSize.height : self.section.itemSize.width;
  76. }
  77. // the first item does not add a separator spacing,
  78. // every one afterwards in the same row will need this spacing constant
  79. if (itemIndex > 0) {
  80. nextItemSize += spacing;
  81. }
  82. // check to see if we can at least fit an item (+separator if necessary)
  83. if (leftOverSpace < nextItemSize) {
  84. break;
  85. }
  86. // we need to maintain the leftover space after the maximum amount of items have
  87. // occupied, so we know how to adjust equal spacing among all the items in a row
  88. leftOverSpace -= nextItemSize;
  89. itemIndex++;
  90. usedItemCount = itemIndex;
  91. }
  92. // push everything to the right if right-aligning and divide in half for centered
  93. // currently there is no public API supporting this behavior
  94. CGPoint itemOffset = CGPointZero;
  95. if (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentRight) {
  96. itemOffset.x += leftOverSpace;
  97. }else if (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentCentered ||
  98. (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentJustify && usedItemCount == 1)) {
  99. // Special case one item row to split leftover space in half
  100. itemOffset.x += leftOverSpace / 2;
  101. }
  102. // calculate the justified spacing among all items in a row if we are using
  103. // the default PSTFlowLayoutHorizontalAlignmentJustify layout
  104. CGFloat interSpacing = usedItemCount <= 1 ? 0 : leftOverSpace / (CGFloat)(usedItemCount - 1);
  105. // calculate row frame as union of all items
  106. CGRect frame = CGRectZero;
  107. CGRect itemFrame = (CGRect){.size=self.section.itemSize};
  108. for (itemIndex = 0; itemIndex < self.itemCount; itemIndex++) {
  109. PSTGridLayoutItem *item = nil;
  110. if (!self.fixedItemSize) {
  111. item = self.items[(NSUInteger)itemIndex];
  112. itemFrame = [item itemFrame];
  113. }
  114. // depending on horizontal/vertical for an item size (height/width),
  115. // we add the minimum separator then an equally distributed spacing
  116. // (since our default mode is justify) calculated from the total leftover
  117. // space divided by the number of intervals
  118. if (isHorizontal) {
  119. itemFrame.origin.y = itemOffset.y;
  120. itemOffset.y += itemFrame.size.height + self.section.verticalInterstice;
  121. if (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentJustify) {
  122. itemOffset.y += interSpacing;
  123. }
  124. }else {
  125. itemFrame.origin.x = itemOffset.x;
  126. itemOffset.x += itemFrame.size.width + self.section.horizontalInterstice;
  127. if (horizontalAlignment == PSTFlowLayoutHorizontalAlignmentJustify) {
  128. itemOffset.x += interSpacing;
  129. }
  130. }
  131. item.itemFrame = itemFrame; // might call nil; don't care
  132. [rects addObject:[NSValue valueWithCGRect:itemFrame]];
  133. frame = CGRectUnion(frame, itemFrame);
  134. }
  135. _rowSize = frame.size;
  136. // _rowFrame = frame; // set externally
  137. _isValid = YES;
  138. }
  139. return rects;
  140. }
  141. - (void)addItem:(PSTGridLayoutItem *)item {
  142. [_items addObject:item];
  143. item.rowObject = self;
  144. [self invalidate];
  145. }
  146. - (PSTGridLayoutRow *)snapshot {
  147. PSTGridLayoutRow *snapshotRow = [self.class new];
  148. snapshotRow.section = self.section;
  149. snapshotRow.items = self.items;
  150. snapshotRow.rowSize = self.rowSize;
  151. snapshotRow.rowFrame = self.rowFrame;
  152. snapshotRow.index = self.index;
  153. snapshotRow.complete = self.complete;
  154. snapshotRow.fixedItemSize = self.fixedItemSize;
  155. snapshotRow.itemCount = self.itemCount;
  156. return snapshotRow;
  157. }
  158. - (PSTGridLayoutRow *)copyFromSection:(PSTGridLayoutSection *)section {
  159. return nil; // ???
  160. }
  161. - (NSInteger)itemCount {
  162. if (self.fixedItemSize) {
  163. return _itemCount;
  164. }else {
  165. return (NSInteger)self.items.count;
  166. }
  167. }
  168. @end