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