├── .gitignore ├── LICENSE ├── NHBalancedFlowLayout.podspec ├── NHBalancedFlowLayout ├── NHBalancedFlowLayout.h ├── NHBalancedFlowLayout.m ├── NHLinearPartition.h └── NHLinearPartition.m ├── NHBalancedFlowLayoutDemo ├── NHBalancedFlowLayoutDemo.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ └── contents.xcworkspacedata ├── NHBalancedFlowLayoutDemo │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── ImageCell.h │ ├── ImageCell.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── LaunchImage.launchimage │ │ │ └── Contents.json │ ├── Main.storyboard │ ├── NHBalancedFlowLayoutDemo-Info.plist │ ├── NHBalancedFlowLayoutDemo-Prefix.pch │ ├── UIImage+Decompression.h │ ├── UIImage+Decompression.m │ ├── ViewController.h │ ├── ViewController.m │ ├── en.lproj │ │ └── InfoPlist.strings │ └── main.m ├── NHBalancedFlowLayoutDemoTests │ ├── BalancedFlowLayoutDemoTests-Info.plist │ ├── LinearPartitionTests.m │ └── en.lproj │ │ └── InfoPlist.strings └── Resources │ └── Images │ ├── photo-01.jpg │ ├── photo-02.jpg │ ├── photo-03.jpg │ ├── photo-04.jpg │ ├── photo-05.jpg │ ├── photo-06.jpg │ ├── photo-07.jpg │ ├── photo-08.jpg │ ├── photo-09.jpg │ ├── photo-10.jpg │ ├── photo-11.jpg │ ├── photo-12.jpg │ ├── photo-13.jpg │ ├── photo-14.jpg │ ├── photo-15.jpg │ ├── photo-16.jpg │ ├── photo-17.jpg │ ├── photo-18.jpg │ ├── photo-19.jpg │ ├── photo-20.jpg │ ├── photo-21.jpg │ ├── photo-22.jpg │ ├── photo-23.jpg │ └── photo-24.jpg └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | *.xccheckout 19 | 20 | #CocoaPods 21 | Pods 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Niels de Hoog 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /NHBalancedFlowLayout.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "NHBalancedFlowLayout" 3 | s.version = "0.2" 4 | s.summary = "UICollectionViewLayout subclass for displaying items of different sizes in a grid without wasting any visual space." 5 | s.homepage = "https://github.com/njdehoog/NHBalancedFlowLayout.git" 6 | s.screenshots = "http://i.imgur.com/2FGnDIh.jpg", "http://i.imgur.com/KRItqy2.jpg" 7 | s.license = { :type => 'MIT', :file => 'LICENSE' } 8 | s.author = { "Niels de Hoog" => "njdehoog@gmail.com" } 9 | s.platform = :ios, '6.0' 10 | s.source = { :git => "https://github.com/njdehoog/NHBalancedFlowLayout.git", :tag => "0.2" } 11 | s.source_files = 'NHBalancedFlowLayout' 12 | s.requires_arc = true 13 | end 14 | -------------------------------------------------------------------------------- /NHBalancedFlowLayout/NHBalancedFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // BalancedFlowLayout.h 3 | // BalancedFlowLayout 4 | // 5 | // Created by Niels de Hoog on 31/10/13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * The BalancedFlowLayout class is designed to display items of different sizes and aspect ratios in a grid, without wasting any visual space. 13 | * It takes the preferred sizes for the displayed items and a preferred row height as input to determine the optimal layout. 14 | * 15 | * In order to use this layout, the delegate for the collection view must implement the required methods in the BalancedFlowLayoutDelegate protocol. 16 | * Currently this class does not support supplementary or decoration views. 17 | * 18 | */ 19 | @interface NHBalancedFlowLayout : UICollectionViewLayout 20 | 21 | // The preferred size for each row measured in the scroll direction 22 | @property (nonatomic) CGFloat preferredRowSize; 23 | 24 | // The size of each section's header. This maybe dynamically adjusted 25 | // per section via the protocol method referenceSizeForHeaderInSection. 26 | @property (nonatomic) CGSize headerReferenceSize; 27 | 28 | // The size of each section's header. This maybe dynamically adjusted 29 | // per section via the protocol method referenceSizeForFooterInSection. 30 | @property (nonatomic) CGSize footerReferenceSize; 31 | 32 | // The margins used to lay out content in a section. 33 | @property (nonatomic) UIEdgeInsets sectionInset; 34 | 35 | // The minimum spacing to use between lines of items in the grid. 36 | @property (nonatomic) CGFloat minimumLineSpacing; 37 | 38 | // The minimum spacing to use between items in the same row. 39 | @property (nonatomic) CGFloat minimumInteritemSpacing; 40 | 41 | // The scroll direction of the grid. 42 | @property (nonatomic) UICollectionViewScrollDirection scrollDirection; 43 | 44 | @end 45 | 46 | 47 | @protocol NHBalancedFlowLayoutDelegate 48 | 49 | @required 50 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(NHBalancedFlowLayout *)collectionViewLayout preferredSizeForItemAtIndexPath:(NSIndexPath *)indexPath; 51 | 52 | @end -------------------------------------------------------------------------------- /NHBalancedFlowLayout/NHBalancedFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // BalancedFlowLayout.m 3 | // BalancedFlowLayout 4 | // 5 | // Created by Niels de Hoog on 31/10/13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import "NHBalancedFlowLayout.h" 10 | #import "NHLinearPartition.h" 11 | 12 | @interface NHBalancedFlowLayout () 13 | { 14 | CGRect **_itemFrameSections; 15 | NSInteger _numberOfItemFrameSections; 16 | 17 | 18 | NSMutableArray *_deleteIndexPaths, *_insertIndexPaths; 19 | CGFloat centerXOffset; 20 | } 21 | 22 | @property (nonatomic) CGSize contentSize; 23 | 24 | @property (nonatomic, strong) NSArray *headerFrames; 25 | @property (nonatomic, strong) NSArray *footerFrames; 26 | 27 | @end 28 | 29 | @implementation NHBalancedFlowLayout 30 | 31 | #pragma mark - Lifecycle 32 | 33 | - (void)clearItemFrames 34 | { 35 | // free all item frame arrays 36 | if (NULL != _itemFrameSections) { 37 | for (NSInteger i = 0; i < _numberOfItemFrameSections; i++) { 38 | CGRect *frames = _itemFrameSections[i]; 39 | free(frames); 40 | } 41 | 42 | free(_itemFrameSections); 43 | _itemFrameSections = NULL; 44 | } 45 | } 46 | 47 | - (void)dealloc 48 | { 49 | [self clearItemFrames]; 50 | } 51 | 52 | - (id)init 53 | { 54 | self = [super init]; 55 | if (self) { 56 | [self initialize]; 57 | } 58 | return self; 59 | } 60 | 61 | - (id)initWithCoder:(NSCoder *)aDecoder 62 | { 63 | self = [super initWithCoder:aDecoder]; 64 | if (self) { 65 | [self initialize]; 66 | } 67 | 68 | return self; 69 | } 70 | 71 | - (void)initialize 72 | { 73 | // set to NULL so it is not released by accident in dealloc 74 | _itemFrameSections = NULL; 75 | 76 | self.sectionInset = UIEdgeInsetsMake(10, 10, 10, 10); 77 | self.minimumLineSpacing = 10; 78 | self.minimumInteritemSpacing = 10; 79 | self.headerReferenceSize = CGSizeZero; 80 | self.footerReferenceSize = CGSizeZero; 81 | self.scrollDirection = UICollectionViewScrollDirectionVertical; 82 | } 83 | 84 | #pragma mark - Layout 85 | 86 | - (void)prepareLayout 87 | { 88 | [super prepareLayout]; 89 | 90 | NSAssert([self.delegate conformsToProtocol:@protocol(NHBalancedFlowLayoutDelegate)], @"UICollectionView delegate should conform to BalancedFlowLayout protocol"); 91 | 92 | CGFloat idealHeight = self.preferredRowSize; 93 | if (idealHeight == 0) { 94 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 95 | idealHeight = CGRectGetHeight(self.collectionView.bounds) / 3.0; 96 | } 97 | else { 98 | idealHeight = CGRectGetWidth(self.collectionView.bounds) / 3.0; 99 | } 100 | } 101 | 102 | NSMutableArray *headerFrames = [NSMutableArray array]; 103 | NSMutableArray *footerFrames = [NSMutableArray array]; 104 | 105 | CGSize contentSize = CGSizeZero; 106 | 107 | // first release old item frame sections 108 | [self clearItemFrames]; 109 | 110 | // create new item frame sections 111 | _numberOfItemFrameSections = [self.collectionView numberOfSections]; 112 | _itemFrameSections = (CGRect **)malloc(sizeof(CGRect *) * _numberOfItemFrameSections); 113 | 114 | for (int section = 0; section < _numberOfItemFrameSections; section++) { 115 | // add new item frames array to sections array 116 | NSInteger numberOfItemsInSections = [self.collectionView numberOfItemsInSection:section]; 117 | CGRect *itemFrames = (CGRect *)malloc(sizeof(CGRect) * numberOfItemsInSections); 118 | _itemFrameSections[section] = itemFrames; 119 | 120 | CGSize headerSize = [self referenceSizeForHeaderInSection:section]; 121 | CGSize sectionSize = CGSizeZero; 122 | 123 | CGRect headerFrame; 124 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 125 | headerFrame = CGRectMake(0, contentSize.height, CGRectGetWidth(self.collectionView.bounds), headerSize.height); 126 | } else { 127 | headerFrame = CGRectMake(contentSize.width, 0, headerSize.width, CGRectGetHeight(self.collectionView.bounds)); 128 | } 129 | [headerFrames addObject:[NSValue valueWithCGRect:headerFrame]]; 130 | 131 | CGFloat totalItemSize = [self totalItemSizeForSection:section preferredRowSize:idealHeight]; 132 | NSInteger numberOfRows = MAX(roundf(totalItemSize / [self viewPortAvailableSize]), 1); 133 | 134 | CGPoint sectionOffset; 135 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 136 | sectionOffset = CGPointMake(0, contentSize.height + headerSize.height); 137 | } else { 138 | sectionOffset = CGPointMake(contentSize.width + headerSize.width, 0); 139 | } 140 | 141 | [self setFrames:itemFrames forItemsInSection:section numberOfRows:numberOfRows sectionOffset:sectionOffset sectionSize:§ionSize]; 142 | 143 | CGSize footerSize = [self referenceSizeForFooterInSection:section]; 144 | CGRect footerFrame; 145 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 146 | footerFrame = CGRectMake(0, contentSize.height + headerSize.height + sectionSize.height, CGRectGetWidth(self.collectionView.bounds), footerSize.height); 147 | } else { 148 | footerFrame = CGRectMake(contentSize.width + headerSize.width + sectionSize.width, 0, footerSize.width, CGRectGetHeight(self.collectionView.bounds)); 149 | } 150 | [footerFrames addObject:[NSValue valueWithCGRect:footerFrame]]; 151 | 152 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 153 | contentSize = CGSizeMake(sectionSize.width, contentSize.height + headerSize.height + sectionSize.height + footerSize.height); 154 | } 155 | else { 156 | contentSize = CGSizeMake(contentSize.width + headerSize.width + sectionSize.width + footerSize.width, sectionSize.height); 157 | } 158 | } 159 | 160 | self.headerFrames = [headerFrames copy]; 161 | self.footerFrames = [footerFrames copy]; 162 | 163 | self.contentSize = contentSize; 164 | CGSize size = self.collectionView.frame.size; 165 | centerXOffset = 2* size.width; 166 | } 167 | 168 | - (void)prepareForCollectionViewUpdates:(NSArray *)updateItems { 169 | // Keep track of insert and delete index paths 170 | [super prepareForCollectionViewUpdates:updateItems]; 171 | 172 | _deleteIndexPaths = [NSMutableArray array]; 173 | _insertIndexPaths = [NSMutableArray array]; 174 | 175 | for (UICollectionViewUpdateItem *update in updateItems) { 176 | if (update.updateAction == UICollectionUpdateActionDelete) { 177 | [_deleteIndexPaths addObject:update.indexPathBeforeUpdate]; 178 | } else if (update.updateAction == UICollectionUpdateActionInsert) { 179 | [_insertIndexPaths addObject:update.indexPathAfterUpdate]; 180 | } 181 | } 182 | } 183 | 184 | - (void)finalizeCollectionViewUpdates { 185 | [super finalizeCollectionViewUpdates]; 186 | // release the insert and delete index paths 187 | _deleteIndexPaths = nil; 188 | _insertIndexPaths = nil; 189 | } 190 | 191 | - (CGSize)collectionViewContentSize 192 | { 193 | return self.contentSize; 194 | } 195 | 196 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 197 | { 198 | NSMutableArray *layoutAttributes = [NSMutableArray array]; 199 | NSInteger n = [self.collectionView numberOfSections]; 200 | 201 | for (NSInteger section = 0; section < n; section++) { 202 | NSIndexPath *sectionIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; 203 | 204 | UICollectionViewLayoutAttributes *headerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader 205 | atIndexPath:sectionIndexPath]; 206 | 207 | CGSize size = headerAttributes.frame.size; 208 | if (size.height != 0 && size.width != 0 && CGRectIntersectsRect(headerAttributes.frame, rect)) { 209 | [layoutAttributes addObject:headerAttributes]; 210 | } 211 | 212 | for (int i = 0; i < [self.collectionView numberOfItemsInSection:section]; i++) { 213 | CGRect itemFrame = _itemFrameSections[section][i]; 214 | if (CGRectIntersectsRect(rect, itemFrame)) { 215 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:i inSection:section]; 216 | [layoutAttributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]]; 217 | } 218 | } 219 | 220 | UICollectionViewLayoutAttributes *footerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter 221 | atIndexPath:sectionIndexPath]; 222 | size = footerAttributes.frame.size; 223 | if (size.width != 0 && size.height != 0 && CGRectIntersectsRect(footerAttributes.frame, rect)) { 224 | [layoutAttributes addObject:footerAttributes]; 225 | } 226 | } 227 | 228 | return layoutAttributes; 229 | } 230 | 231 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 232 | { 233 | UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 234 | attributes.frame = [self itemFrameForIndexPath:indexPath]; 235 | 236 | return attributes; 237 | } 238 | 239 | - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 240 | { 241 | UICollectionViewLayoutAttributes *attributes = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:kind withIndexPath:indexPath]; 242 | 243 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 244 | attributes.frame = [self headerFrameForSection:indexPath.section]; 245 | } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 246 | attributes.frame = [self footerFrameForSection:indexPath.section]; 247 | } 248 | 249 | // If there is no header or footer, we need to return nil to prevent a crash from UICollectionView private methods. 250 | if(CGRectIsEmpty(attributes.frame)) { 251 | attributes = nil; 252 | } 253 | 254 | return attributes; 255 | } 256 | 257 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 258 | { 259 | CGRect oldBounds = self.collectionView.bounds; 260 | if (CGRectGetWidth(newBounds) != CGRectGetWidth(oldBounds) || CGRectGetHeight(newBounds) != CGRectGetHeight(oldBounds)) { 261 | return YES; 262 | } 263 | 264 | return NO; 265 | } 266 | 267 | - (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath 268 | { 269 | // Must call super 270 | UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; 271 | 272 | if ([_insertIndexPaths containsObject:itemIndexPath]) { 273 | // only change attributes on inserted cells 274 | if (!attributes) 275 | attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; 276 | 277 | // Configure attributes ... 278 | attributes.alpha = 0.5; 279 | CGPoint center = attributes.center; 280 | attributes.center = CGPointMake(center.x+centerXOffset, center.y); 281 | } 282 | 283 | return attributes; 284 | } 285 | 286 | // Note: name of method changed 287 | // Also this gets called for all visible cells (not just the deleted ones) and 288 | // even gets called when inserting cells! 289 | - (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath 290 | { 291 | // So far, calling super hasn't been strictly necessary here, but leaving it in 292 | // for good measure 293 | UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; 294 | 295 | if ([_deleteIndexPaths containsObject:itemIndexPath]) 296 | { 297 | // only change attributes on deleted cells 298 | if (!attributes) 299 | attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; 300 | 301 | // Configure attributes ... 302 | attributes.alpha = 0.5; 303 | CGPoint center = attributes.center; 304 | attributes.center = CGPointMake(center.x-centerXOffset, center.y); 305 | //attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0); 306 | } 307 | 308 | return attributes; 309 | } 310 | 311 | #pragma mark - Layout helpers 312 | 313 | - (CGRect)headerFrameForSection:(NSInteger)section 314 | { 315 | return [[self.headerFrames objectAtIndex:section] CGRectValue]; 316 | } 317 | 318 | - (CGRect)itemFrameForIndexPath:(NSIndexPath *)indexPath 319 | { 320 | return _itemFrameSections[indexPath.section][indexPath.item]; 321 | } 322 | 323 | - (CGRect)footerFrameForSection:(NSInteger)section 324 | { 325 | return [[self.footerFrames objectAtIndex:section] CGRectValue]; 326 | } 327 | 328 | - (CGFloat)totalItemSizeForSection:(NSInteger)section preferredRowSize:(CGFloat)preferredRowSize 329 | { 330 | CGFloat totalItemSize = 0; 331 | NSUInteger n = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:section]; 332 | 333 | for (NSInteger i = 0; i < n; i++) { 334 | CGSize preferredSize = [self.delegate collectionView:self.collectionView layout:self preferredSizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:section]]; 335 | 336 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 337 | totalItemSize += (preferredSize.width / preferredSize.height) * preferredRowSize; 338 | } 339 | else { 340 | totalItemSize += (preferredSize.height / preferredSize.width) * preferredRowSize; 341 | } 342 | } 343 | 344 | return totalItemSize; 345 | } 346 | 347 | - (NSArray *)weightsForItemsInSection:(NSInteger)section 348 | { 349 | NSMutableArray *weights = [NSMutableArray array]; 350 | for (NSInteger i = 0, n = [self.collectionView numberOfItemsInSection:section]; i < n; i++) { 351 | CGSize preferredSize = [self.delegate collectionView:self.collectionView layout:self preferredSizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:section]]; 352 | NSInteger aspectRatio = self.scrollDirection == UICollectionViewScrollDirectionVertical ? roundf((preferredSize.width / preferredSize.height) * 100) : roundf((preferredSize.height / preferredSize.width) * 100); 353 | [weights addObject:@(aspectRatio)]; 354 | } 355 | 356 | return [weights copy]; 357 | } 358 | 359 | - (void)setFrames:(CGRect *)frames forItemsInSection:(NSInteger)section numberOfRows:(NSUInteger)numberOfRows sectionOffset:(CGPoint)sectionOffset sectionSize:(CGSize *)sectionSize 360 | { 361 | NSArray *weights = [self weightsForItemsInSection:section]; 362 | 363 | if (weights.count == 0) { 364 | *sectionSize = CGSizeZero; 365 | return; 366 | } 367 | 368 | NSMutableArray *partition = [NHLinearPartition linearPartitionForSequence:weights numberOfPartitions:numberOfRows]; 369 | 370 | // workaround to remove single images in a row 371 | for (NSInteger i = 0; i < partition.count; i++) { 372 | NSArray *row = partition[i]; 373 | if (row.count == 1) { 374 | NSArray *prev = i > 0 ? partition[i-1] : nil; 375 | NSArray *next = i < partition.count-1 ? partition[i+1] : nil; 376 | if (prev || next) { 377 | // stick the image in the row with less images in it 378 | if (next == nil || (prev != nil && prev.count < next.count)) { 379 | partition[i-1] = [prev arrayByAddingObject:row[0]]; 380 | } else { 381 | NSMutableArray *arr = [next mutableCopy]; 382 | [arr insertObject:row[0] atIndex:0]; 383 | partition[i+1] = arr; 384 | } 385 | [partition removeObjectAtIndex:i]; 386 | i--; 387 | } 388 | } 389 | } 390 | 391 | int i = 0; 392 | CGPoint offset = CGPointMake(sectionOffset.x + self.sectionInset.left, sectionOffset.y + self.sectionInset.top); 393 | CGFloat previousItemSize = 0; 394 | CGFloat contentMaxValueInScrollDirection = 0; 395 | for (NSArray *row in partition) { 396 | 397 | CGFloat summedRatios = 0; 398 | for (NSInteger j = i, n = i + [row count]; j < n; j++) { 399 | CGSize preferredSize = [self.delegate collectionView:self.collectionView layout:self preferredSizeForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:section]]; 400 | 401 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 402 | summedRatios += preferredSize.width / preferredSize.height; 403 | } 404 | else { 405 | summedRatios += preferredSize.height / preferredSize.width; 406 | } 407 | } 408 | 409 | CGFloat rowSize = [self viewPortAvailableSize] - (([row count] - 1) * self.minimumInteritemSpacing); 410 | for (NSInteger j = i, n = i + [row count]; j < n; j++) { 411 | CGSize preferredSize = [self.delegate collectionView:self.collectionView layout:self preferredSizeForItemAtIndexPath:[NSIndexPath indexPathForItem:j inSection:section]]; 412 | 413 | CGSize actualSize = CGSizeZero; 414 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 415 | actualSize = CGSizeMake(roundf(rowSize / summedRatios * (preferredSize.width / preferredSize.height)), roundf(rowSize / summedRatios)); 416 | } 417 | else { 418 | actualSize = CGSizeMake(roundf(rowSize / summedRatios), roundf(rowSize / summedRatios * (preferredSize.height / preferredSize.width))); 419 | } 420 | 421 | CGRect frame = CGRectMake(offset.x, offset.y, actualSize.width, actualSize.height); 422 | // copy frame into frames ptr and increment ptr 423 | *frames++ = frame; 424 | 425 | 426 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 427 | offset.x += actualSize.width + self.minimumInteritemSpacing; 428 | previousItemSize = actualSize.height; 429 | contentMaxValueInScrollDirection = CGRectGetMaxY(frame); 430 | } 431 | else { 432 | offset.y += actualSize.height + self.minimumInteritemSpacing; 433 | previousItemSize = actualSize.width; 434 | contentMaxValueInScrollDirection = CGRectGetMaxX(frame); 435 | } 436 | } 437 | 438 | /** 439 | * Check if row actually contains any items before changing offset, 440 | * because linear partitioning algorithm might return a row with no items. 441 | */ 442 | if ([row count] > 0) { 443 | // move offset to next line 444 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 445 | offset = CGPointMake(self.sectionInset.left, offset.y + previousItemSize + self.minimumLineSpacing); 446 | } 447 | else { 448 | offset = CGPointMake(offset.x + previousItemSize + self.minimumLineSpacing, self.sectionInset.top); 449 | } 450 | } 451 | 452 | i += [row count]; 453 | } 454 | 455 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 456 | *sectionSize = CGSizeMake([self viewPortWidth], (contentMaxValueInScrollDirection - sectionOffset.y) + self.sectionInset.bottom); 457 | } 458 | else { 459 | *sectionSize = CGSizeMake((contentMaxValueInScrollDirection - sectionOffset.x) + self.sectionInset.right, [self viewPortHeight]); 460 | } 461 | } 462 | 463 | - (CGFloat)viewPortWidth 464 | { 465 | return CGRectGetWidth(self.collectionView.frame) - self.collectionView.contentInset.left - self.collectionView.contentInset.right; 466 | } 467 | 468 | - (CGFloat)viewPortHeight 469 | { 470 | return (CGRectGetHeight(self.collectionView.frame) - self.collectionView.contentInset.top - self.collectionView.contentInset.bottom); 471 | } 472 | 473 | - (CGFloat)viewPortAvailableSize 474 | { 475 | CGFloat availableSize = 0; 476 | if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { 477 | availableSize = [self viewPortWidth] - self.sectionInset.left - self.sectionInset.right; 478 | } 479 | else { 480 | availableSize = [self viewPortHeight] - self.sectionInset.top - self.sectionInset.bottom; 481 | } 482 | 483 | return availableSize; 484 | } 485 | 486 | #pragma mark - Custom setters 487 | 488 | - (void)setPreferredRowSize:(CGFloat)preferredRowHeight 489 | { 490 | _preferredRowSize = preferredRowHeight; 491 | 492 | [self invalidateLayout]; 493 | } 494 | 495 | - (void)setSectionInset:(UIEdgeInsets)sectionInset 496 | { 497 | _sectionInset = sectionInset; 498 | 499 | [self invalidateLayout]; 500 | } 501 | 502 | - (void)setMinimumLineSpacing:(CGFloat)minimumLineSpacing 503 | { 504 | _minimumLineSpacing = minimumLineSpacing; 505 | 506 | [self invalidateLayout]; 507 | } 508 | 509 | - (void)setMinimumInteritemSpacing:(CGFloat)minimumInteritemSpacing 510 | { 511 | _minimumInteritemSpacing = minimumInteritemSpacing; 512 | 513 | [self invalidateLayout]; 514 | } 515 | 516 | - (void)setHeaderReferenceSize:(CGSize)headerReferenceSize 517 | { 518 | _headerReferenceSize = headerReferenceSize; 519 | 520 | [self invalidateLayout]; 521 | } 522 | 523 | - (void)setFooterReferenceSize:(CGSize)footerReferenceSize 524 | { 525 | _footerReferenceSize = footerReferenceSize; 526 | 527 | [self invalidateLayout]; 528 | } 529 | 530 | #pragma mark - Delegate 531 | 532 | - (id)delegate 533 | { 534 | return (id)self.collectionView.delegate; 535 | } 536 | 537 | #pragma mark - Delegate helpers 538 | 539 | - (CGSize)referenceSizeForHeaderInSection:(NSInteger)section 540 | { 541 | BOOL respondsToSelector = [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForHeaderInSection:)]; 542 | if (respondsToSelector) { 543 | return [(id )self.collectionView.delegate collectionView:self.collectionView layout:self referenceSizeForHeaderInSection:section]; 544 | } 545 | return self.headerReferenceSize; 546 | } 547 | 548 | - (CGSize)referenceSizeForFooterInSection:(NSInteger)section 549 | { 550 | BOOL respondsToSelector = [self.collectionView.delegate respondsToSelector:@selector(collectionView:layout:referenceSizeForFooterInSection:)]; 551 | if (respondsToSelector) { 552 | return [(id )self.collectionView.delegate collectionView:self.collectionView layout:self referenceSizeForFooterInSection:section]; 553 | } 554 | return self.footerReferenceSize; 555 | } 556 | 557 | 558 | @end 559 | -------------------------------------------------------------------------------- /NHBalancedFlowLayout/NHLinearPartition.h: -------------------------------------------------------------------------------- 1 | // 2 | // LinearPartition.h 3 | // BalancedFlowLayout 4 | // 5 | // Created by Niels de Hoog on 08-10-13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * Partitions a sequence of non-negative integers into the required number of partitions. 13 | * Based on implementation in Python by Óscar López: http://stackoverflow.com/a/7942946 14 | * Example: [LinearPartition linearPartitionForSequence:@[9,2,6,3,8,5,8,1,7,3,4] numberOfPartitions:3] => @[@[9,2,6,3],@[8,5,8],@[1,7,3,4]] 15 | */ 16 | @interface NHLinearPartition : NSObject 17 | 18 | + (NSMutableArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /NHBalancedFlowLayout/NHLinearPartition.m: -------------------------------------------------------------------------------- 1 | // 2 | // LinearPartition.m 3 | // BalancedFlowLayout 4 | // 5 | // Created by Niels de Hoog on 08-10-13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import "NHLinearPartition.h" 10 | 11 | 12 | #define NH_LP_TABLE_LOOKUP(table, i, j, rowsize) (table)[(i) * (rowsize) + (j)] 13 | 14 | 15 | @implementation NHLinearPartition 16 | 17 | /** 18 | * Returns a solution integer array for the linear partition problem 19 | * 20 | * @param sequence An array of NSNumber's 21 | * @param numPartitions The number of partitions wanted 22 | * 23 | * @return A C-style solution table 24 | */ 25 | + (NSInteger *)linearPartitionTable:(NSArray *)sequence numPartitions:(NSInteger)numPartitions 26 | { 27 | NSInteger k = numPartitions; 28 | NSInteger n = [sequence count]; 29 | 30 | // allocate a table buffer of n * k integers 31 | NSInteger tableSize = sizeof(NSInteger) * n * k; 32 | NSInteger *tmpTable = (NSInteger *)malloc(tableSize); 33 | memset(tmpTable, 0, tableSize); 34 | 35 | // allocate a solution buffer of (n - 1) * (k - 1) integers 36 | NSInteger solutionSize = sizeof(NSInteger) * (n - 1) * (k - 1); 37 | NSInteger *solution = (NSInteger *)malloc(solutionSize); 38 | memset(solution, 0, solutionSize); 39 | 40 | // fill table with initial values 41 | for (NSInteger i = 0; i < n; i++) { 42 | NSInteger offset = i? NH_LP_TABLE_LOOKUP(tmpTable, i - 1, 0, k) : 0; 43 | NH_LP_TABLE_LOOKUP(tmpTable, i, 0, k) = [sequence[i] integerValue] + offset; 44 | } 45 | 46 | for (NSInteger j = 0; j < k; j++) { 47 | NH_LP_TABLE_LOOKUP(tmpTable, 0, j, k) = [sequence[0] integerValue]; 48 | } 49 | 50 | // calculate the costs and fill the solution buffer 51 | for (NSInteger i = 1; i < n; i++) { 52 | for (NSInteger j = 1; j < k; j++) { 53 | NSInteger currentMin = 0; 54 | NSInteger minX = NSIntegerMax; 55 | 56 | for (NSInteger x = 0; x < i; x++) { 57 | NSInteger c1 = NH_LP_TABLE_LOOKUP(tmpTable, x, j - 1, k); 58 | NSInteger c2 = NH_LP_TABLE_LOOKUP(tmpTable, i, 0, k) - NH_LP_TABLE_LOOKUP(tmpTable, x, 0, k); 59 | NSInteger cost = MAX(c1, c2); 60 | 61 | if (!x || cost < currentMin) { 62 | currentMin = cost; 63 | minX = x; 64 | } 65 | } 66 | 67 | NH_LP_TABLE_LOOKUP(tmpTable, i, j, k) = currentMin; 68 | NH_LP_TABLE_LOOKUP(solution, i - 1, j - 1, k - 1) = minX; 69 | } 70 | } 71 | 72 | // free the tmp table, we don't need it anymore 73 | free(tmpTable); 74 | 75 | return solution; 76 | } 77 | 78 | + (NSMutableArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions 79 | { 80 | NSInteger n = [sequence count]; 81 | NSInteger k = numberOfPartitions; 82 | 83 | if (k <= 0) return [NSMutableArray array]; 84 | 85 | if (k >= n) { 86 | NSMutableArray *partition = [[NSMutableArray alloc] init]; 87 | [sequence enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 88 | [partition addObject:@[obj]]; 89 | }]; 90 | return [partition mutableCopy]; 91 | } 92 | 93 | if (n == 1) { 94 | return [NSMutableArray arrayWithObject:sequence]; 95 | } 96 | 97 | // get the solution table 98 | NSInteger *solution = [self linearPartitionTable:sequence numPartitions:numberOfPartitions]; 99 | NSInteger solutionRowSize = numberOfPartitions - 1; 100 | 101 | k = k - 2; 102 | n = n - 1; 103 | NSMutableArray *answer = [NSMutableArray array]; 104 | 105 | while (k >= 0) { 106 | if (n < 1) { 107 | [answer insertObject:@[] atIndex:0]; 108 | } else { 109 | NSMutableArray *currentAnswer = [NSMutableArray array]; 110 | for (NSInteger i = NH_LP_TABLE_LOOKUP(solution, n - 1, k, solutionRowSize) + 1, range = n + 1; i < range; i++) { 111 | [currentAnswer addObject:sequence[i]]; 112 | } 113 | [answer insertObject:currentAnswer atIndex:0]; 114 | 115 | n = NH_LP_TABLE_LOOKUP(solution, n - 1, k, solutionRowSize); 116 | } 117 | 118 | k = k - 1; 119 | } 120 | 121 | // free the solution table because we don't need it anymore 122 | free(solution); 123 | 124 | NSMutableArray *currentAnswer = [NSMutableArray array]; 125 | for (NSInteger i = 0, range = n + 1; i < range; i++) { 126 | [currentAnswer addObject:sequence[i]]; 127 | } 128 | 129 | [answer insertObject:currentAnswer atIndex:0]; 130 | 131 | return answer; 132 | } 133 | 134 | @end 135 | 136 | 137 | 138 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 50CAF4C8180430A6009A7FA1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50CAF4C7180430A6009A7FA1 /* Foundation.framework */; }; 11 | 50CAF4CA180430A6009A7FA1 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50CAF4C9180430A6009A7FA1 /* CoreGraphics.framework */; }; 12 | 50CAF4CC180430A6009A7FA1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50CAF4CB180430A6009A7FA1 /* UIKit.framework */; }; 13 | 50CAF4D2180430A6009A7FA1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 50CAF4D0180430A6009A7FA1 /* InfoPlist.strings */; }; 14 | 50CAF4D4180430A6009A7FA1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 50CAF4D3180430A6009A7FA1 /* main.m */; }; 15 | 50CAF4D8180430A6009A7FA1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 50CAF4D7180430A6009A7FA1 /* AppDelegate.m */; }; 16 | 50CAF4DE180430A6009A7FA1 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 50CAF4DD180430A6009A7FA1 /* ViewController.m */; }; 17 | 50CAF4E0180430A6009A7FA1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 50CAF4DF180430A6009A7FA1 /* Images.xcassets */; }; 18 | 50CAF4E7180430A6009A7FA1 /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50CAF4E6180430A6009A7FA1 /* XCTest.framework */; }; 19 | 50CAF4E8180430A6009A7FA1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50CAF4C7180430A6009A7FA1 /* Foundation.framework */; }; 20 | 50CAF4E9180430A6009A7FA1 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50CAF4CB180430A6009A7FA1 /* UIKit.framework */; }; 21 | 50CAF4F1180430A6009A7FA1 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 50CAF4EF180430A6009A7FA1 /* InfoPlist.strings */; }; 22 | FD607149181E4C8B00593765 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FD607148181E4C8B00593765 /* Main.storyboard */; }; 23 | FD9D1F69181FBFC600F17457 /* ImageCell.m in Sources */ = {isa = PBXBuildFile; fileRef = FD9D1F68181FBFC600F17457 /* ImageCell.m */; }; 24 | FD9D1F84181FC08500F17457 /* photo-01.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F6C181FC08500F17457 /* photo-01.jpg */; }; 25 | FD9D1F85181FC08500F17457 /* photo-02.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F6D181FC08500F17457 /* photo-02.jpg */; }; 26 | FD9D1F86181FC08500F17457 /* photo-03.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F6E181FC08500F17457 /* photo-03.jpg */; }; 27 | FD9D1F87181FC08500F17457 /* photo-04.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F6F181FC08500F17457 /* photo-04.jpg */; }; 28 | FD9D1F88181FC08500F17457 /* photo-05.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F70181FC08500F17457 /* photo-05.jpg */; }; 29 | FD9D1F89181FC08500F17457 /* photo-06.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F71181FC08500F17457 /* photo-06.jpg */; }; 30 | FD9D1F8A181FC08500F17457 /* photo-07.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F72181FC08500F17457 /* photo-07.jpg */; }; 31 | FD9D1F8B181FC08500F17457 /* photo-08.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F73181FC08500F17457 /* photo-08.jpg */; }; 32 | FD9D1F8C181FC08500F17457 /* photo-09.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F74181FC08500F17457 /* photo-09.jpg */; }; 33 | FD9D1F8D181FC08500F17457 /* photo-10.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F75181FC08500F17457 /* photo-10.jpg */; }; 34 | FD9D1F8E181FC08500F17457 /* photo-11.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F76181FC08500F17457 /* photo-11.jpg */; }; 35 | FD9D1F8F181FC08500F17457 /* photo-12.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F77181FC08500F17457 /* photo-12.jpg */; }; 36 | FD9D1F90181FC08500F17457 /* photo-13.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F78181FC08500F17457 /* photo-13.jpg */; }; 37 | FD9D1F91181FC08500F17457 /* photo-14.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F79181FC08500F17457 /* photo-14.jpg */; }; 38 | FD9D1F92181FC08500F17457 /* photo-15.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F7A181FC08500F17457 /* photo-15.jpg */; }; 39 | FD9D1F93181FC08500F17457 /* photo-16.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F7B181FC08500F17457 /* photo-16.jpg */; }; 40 | FD9D1F94181FC08500F17457 /* photo-17.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F7C181FC08500F17457 /* photo-17.jpg */; }; 41 | FD9D1F95181FC08500F17457 /* photo-18.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F7D181FC08500F17457 /* photo-18.jpg */; }; 42 | FD9D1F96181FC08500F17457 /* photo-19.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F7E181FC08500F17457 /* photo-19.jpg */; }; 43 | FD9D1F97181FC08500F17457 /* photo-20.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F7F181FC08500F17457 /* photo-20.jpg */; }; 44 | FD9D1F98181FC08500F17457 /* photo-21.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F80181FC08500F17457 /* photo-21.jpg */; }; 45 | FD9D1F99181FC08500F17457 /* photo-22.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F81181FC08500F17457 /* photo-22.jpg */; }; 46 | FD9D1F9A181FC08500F17457 /* photo-23.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F82181FC08500F17457 /* photo-23.jpg */; }; 47 | FD9D1F9B181FC08500F17457 /* photo-24.jpg in Resources */ = {isa = PBXBuildFile; fileRef = FD9D1F83181FC08500F17457 /* photo-24.jpg */; }; 48 | FDC8070D182B84C600ADF578 /* NHBalancedFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = FDC8070A182B84C600ADF578 /* NHBalancedFlowLayout.m */; }; 49 | FDC8070E182B84C600ADF578 /* NHLinearPartition.m in Sources */ = {isa = PBXBuildFile; fileRef = FDC8070C182B84C600ADF578 /* NHLinearPartition.m */; }; 50 | FDDD339B18218D6500314905 /* UIImage+Decompression.m in Sources */ = {isa = PBXBuildFile; fileRef = FDDD339A18218D6500314905 /* UIImage+Decompression.m */; }; 51 | FDFDF5141851D25500922D68 /* LinearPartitionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = FDFDF5131851D25500922D68 /* LinearPartitionTests.m */; }; 52 | /* End PBXBuildFile section */ 53 | 54 | /* Begin PBXContainerItemProxy section */ 55 | 50CAF4EA180430A6009A7FA1 /* PBXContainerItemProxy */ = { 56 | isa = PBXContainerItemProxy; 57 | containerPortal = 50CAF4BC180430A6009A7FA1 /* Project object */; 58 | proxyType = 1; 59 | remoteGlobalIDString = 50CAF4C3180430A6009A7FA1; 60 | remoteInfo = LinearPartition; 61 | }; 62 | /* End PBXContainerItemProxy section */ 63 | 64 | /* Begin PBXFileReference section */ 65 | 50CAF4C4180430A6009A7FA1 /* NHBalancedFlowLayoutDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NHBalancedFlowLayoutDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 66 | 50CAF4C7180430A6009A7FA1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 67 | 50CAF4C9180430A6009A7FA1 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 68 | 50CAF4CB180430A6009A7FA1 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 69 | 50CAF4CF180430A6009A7FA1 /* NHBalancedFlowLayoutDemo-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "NHBalancedFlowLayoutDemo-Info.plist"; sourceTree = ""; }; 70 | 50CAF4D1180430A6009A7FA1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 71 | 50CAF4D3180430A6009A7FA1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 72 | 50CAF4D5180430A6009A7FA1 /* NHBalancedFlowLayoutDemo-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NHBalancedFlowLayoutDemo-Prefix.pch"; sourceTree = ""; }; 73 | 50CAF4D6180430A6009A7FA1 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 74 | 50CAF4D7180430A6009A7FA1 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 75 | 50CAF4DC180430A6009A7FA1 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 76 | 50CAF4DD180430A6009A7FA1 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 77 | 50CAF4DF180430A6009A7FA1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 78 | 50CAF4E5180430A6009A7FA1 /* NHBalancedFlowLayoutDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = NHBalancedFlowLayoutDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 79 | 50CAF4E6180430A6009A7FA1 /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; }; 80 | 50CAF4EE180430A6009A7FA1 /* BalancedFlowLayoutDemoTests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "BalancedFlowLayoutDemoTests-Info.plist"; sourceTree = ""; }; 81 | 50CAF4F0180430A6009A7FA1 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 82 | FD607148181E4C8B00593765 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 83 | FD9D1F67181FBFC600F17457 /* ImageCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageCell.h; sourceTree = ""; }; 84 | FD9D1F68181FBFC600F17457 /* ImageCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ImageCell.m; sourceTree = ""; }; 85 | FD9D1F6C181FC08500F17457 /* photo-01.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-01.jpg"; sourceTree = ""; }; 86 | FD9D1F6D181FC08500F17457 /* photo-02.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-02.jpg"; sourceTree = ""; }; 87 | FD9D1F6E181FC08500F17457 /* photo-03.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-03.jpg"; sourceTree = ""; }; 88 | FD9D1F6F181FC08500F17457 /* photo-04.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-04.jpg"; sourceTree = ""; }; 89 | FD9D1F70181FC08500F17457 /* photo-05.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-05.jpg"; sourceTree = ""; }; 90 | FD9D1F71181FC08500F17457 /* photo-06.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-06.jpg"; sourceTree = ""; }; 91 | FD9D1F72181FC08500F17457 /* photo-07.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-07.jpg"; sourceTree = ""; }; 92 | FD9D1F73181FC08500F17457 /* photo-08.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-08.jpg"; sourceTree = ""; }; 93 | FD9D1F74181FC08500F17457 /* photo-09.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-09.jpg"; sourceTree = ""; }; 94 | FD9D1F75181FC08500F17457 /* photo-10.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-10.jpg"; sourceTree = ""; }; 95 | FD9D1F76181FC08500F17457 /* photo-11.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-11.jpg"; sourceTree = ""; }; 96 | FD9D1F77181FC08500F17457 /* photo-12.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-12.jpg"; sourceTree = ""; }; 97 | FD9D1F78181FC08500F17457 /* photo-13.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-13.jpg"; sourceTree = ""; }; 98 | FD9D1F79181FC08500F17457 /* photo-14.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-14.jpg"; sourceTree = ""; }; 99 | FD9D1F7A181FC08500F17457 /* photo-15.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-15.jpg"; sourceTree = ""; }; 100 | FD9D1F7B181FC08500F17457 /* photo-16.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-16.jpg"; sourceTree = ""; }; 101 | FD9D1F7C181FC08500F17457 /* photo-17.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-17.jpg"; sourceTree = ""; }; 102 | FD9D1F7D181FC08500F17457 /* photo-18.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-18.jpg"; sourceTree = ""; }; 103 | FD9D1F7E181FC08500F17457 /* photo-19.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-19.jpg"; sourceTree = ""; }; 104 | FD9D1F7F181FC08500F17457 /* photo-20.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-20.jpg"; sourceTree = ""; }; 105 | FD9D1F80181FC08500F17457 /* photo-21.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-21.jpg"; sourceTree = ""; }; 106 | FD9D1F81181FC08500F17457 /* photo-22.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-22.jpg"; sourceTree = ""; }; 107 | FD9D1F82181FC08500F17457 /* photo-23.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-23.jpg"; sourceTree = ""; }; 108 | FD9D1F83181FC08500F17457 /* photo-24.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = "photo-24.jpg"; sourceTree = ""; }; 109 | FDC80709182B84C600ADF578 /* NHBalancedFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NHBalancedFlowLayout.h; sourceTree = ""; }; 110 | FDC8070A182B84C600ADF578 /* NHBalancedFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NHBalancedFlowLayout.m; sourceTree = ""; }; 111 | FDC8070B182B84C600ADF578 /* NHLinearPartition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NHLinearPartition.h; sourceTree = ""; }; 112 | FDC8070C182B84C600ADF578 /* NHLinearPartition.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NHLinearPartition.m; sourceTree = ""; }; 113 | FDDD339918218D6500314905 /* UIImage+Decompression.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIImage+Decompression.h"; sourceTree = ""; }; 114 | FDDD339A18218D6500314905 /* UIImage+Decompression.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIImage+Decompression.m"; sourceTree = ""; }; 115 | FDFDF5131851D25500922D68 /* LinearPartitionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LinearPartitionTests.m; sourceTree = ""; }; 116 | /* End PBXFileReference section */ 117 | 118 | /* Begin PBXFrameworksBuildPhase section */ 119 | 50CAF4C1180430A6009A7FA1 /* Frameworks */ = { 120 | isa = PBXFrameworksBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | 50CAF4CA180430A6009A7FA1 /* CoreGraphics.framework in Frameworks */, 124 | 50CAF4CC180430A6009A7FA1 /* UIKit.framework in Frameworks */, 125 | 50CAF4C8180430A6009A7FA1 /* Foundation.framework in Frameworks */, 126 | ); 127 | runOnlyForDeploymentPostprocessing = 0; 128 | }; 129 | 50CAF4E2180430A6009A7FA1 /* Frameworks */ = { 130 | isa = PBXFrameworksBuildPhase; 131 | buildActionMask = 2147483647; 132 | files = ( 133 | 50CAF4E7180430A6009A7FA1 /* XCTest.framework in Frameworks */, 134 | 50CAF4E9180430A6009A7FA1 /* UIKit.framework in Frameworks */, 135 | 50CAF4E8180430A6009A7FA1 /* Foundation.framework in Frameworks */, 136 | ); 137 | runOnlyForDeploymentPostprocessing = 0; 138 | }; 139 | /* End PBXFrameworksBuildPhase section */ 140 | 141 | /* Begin PBXGroup section */ 142 | 50CAF4BB180430A6009A7FA1 = { 143 | isa = PBXGroup; 144 | children = ( 145 | FDC80708182B849F00ADF578 /* NHBalancedFlowLayout */, 146 | 50CAF4CD180430A6009A7FA1 /* NHBalancedFlowLayoutDemo */, 147 | FD9D1F6A181FC08500F17457 /* Resources */, 148 | 50CAF4EC180430A6009A7FA1 /* NHBalancedFlowLayoutDemoTests */, 149 | 50CAF4C6180430A6009A7FA1 /* Frameworks */, 150 | 50CAF4C5180430A6009A7FA1 /* Products */, 151 | ); 152 | sourceTree = ""; 153 | }; 154 | 50CAF4C5180430A6009A7FA1 /* Products */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | 50CAF4C4180430A6009A7FA1 /* NHBalancedFlowLayoutDemo.app */, 158 | 50CAF4E5180430A6009A7FA1 /* NHBalancedFlowLayoutDemoTests.xctest */, 159 | ); 160 | name = Products; 161 | sourceTree = ""; 162 | }; 163 | 50CAF4C6180430A6009A7FA1 /* Frameworks */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 50CAF4C7180430A6009A7FA1 /* Foundation.framework */, 167 | 50CAF4C9180430A6009A7FA1 /* CoreGraphics.framework */, 168 | 50CAF4CB180430A6009A7FA1 /* UIKit.framework */, 169 | 50CAF4E6180430A6009A7FA1 /* XCTest.framework */, 170 | ); 171 | name = Frameworks; 172 | sourceTree = ""; 173 | }; 174 | 50CAF4CD180430A6009A7FA1 /* NHBalancedFlowLayoutDemo */ = { 175 | isa = PBXGroup; 176 | children = ( 177 | 50CAF4D6180430A6009A7FA1 /* AppDelegate.h */, 178 | 50CAF4D7180430A6009A7FA1 /* AppDelegate.m */, 179 | FD607148181E4C8B00593765 /* Main.storyboard */, 180 | 50CAF4DC180430A6009A7FA1 /* ViewController.h */, 181 | 50CAF4DD180430A6009A7FA1 /* ViewController.m */, 182 | FD9D1F67181FBFC600F17457 /* ImageCell.h */, 183 | FD9D1F68181FBFC600F17457 /* ImageCell.m */, 184 | FDDD339918218D6500314905 /* UIImage+Decompression.h */, 185 | FDDD339A18218D6500314905 /* UIImage+Decompression.m */, 186 | 50CAF4DF180430A6009A7FA1 /* Images.xcassets */, 187 | 50CAF4CE180430A6009A7FA1 /* Supporting Files */, 188 | ); 189 | path = NHBalancedFlowLayoutDemo; 190 | sourceTree = ""; 191 | }; 192 | 50CAF4CE180430A6009A7FA1 /* Supporting Files */ = { 193 | isa = PBXGroup; 194 | children = ( 195 | 50CAF4CF180430A6009A7FA1 /* NHBalancedFlowLayoutDemo-Info.plist */, 196 | 50CAF4D0180430A6009A7FA1 /* InfoPlist.strings */, 197 | 50CAF4D3180430A6009A7FA1 /* main.m */, 198 | 50CAF4D5180430A6009A7FA1 /* NHBalancedFlowLayoutDemo-Prefix.pch */, 199 | ); 200 | name = "Supporting Files"; 201 | sourceTree = ""; 202 | }; 203 | 50CAF4EC180430A6009A7FA1 /* NHBalancedFlowLayoutDemoTests */ = { 204 | isa = PBXGroup; 205 | children = ( 206 | FDFDF5131851D25500922D68 /* LinearPartitionTests.m */, 207 | 50CAF4ED180430A6009A7FA1 /* Supporting Files */, 208 | ); 209 | path = NHBalancedFlowLayoutDemoTests; 210 | sourceTree = ""; 211 | }; 212 | 50CAF4ED180430A6009A7FA1 /* Supporting Files */ = { 213 | isa = PBXGroup; 214 | children = ( 215 | 50CAF4EE180430A6009A7FA1 /* BalancedFlowLayoutDemoTests-Info.plist */, 216 | 50CAF4EF180430A6009A7FA1 /* InfoPlist.strings */, 217 | ); 218 | name = "Supporting Files"; 219 | sourceTree = ""; 220 | }; 221 | FD9D1F6A181FC08500F17457 /* Resources */ = { 222 | isa = PBXGroup; 223 | children = ( 224 | FD9D1F6B181FC08500F17457 /* Images */, 225 | ); 226 | path = Resources; 227 | sourceTree = ""; 228 | }; 229 | FD9D1F6B181FC08500F17457 /* Images */ = { 230 | isa = PBXGroup; 231 | children = ( 232 | FD9D1F6C181FC08500F17457 /* photo-01.jpg */, 233 | FD9D1F6D181FC08500F17457 /* photo-02.jpg */, 234 | FD9D1F6E181FC08500F17457 /* photo-03.jpg */, 235 | FD9D1F6F181FC08500F17457 /* photo-04.jpg */, 236 | FD9D1F70181FC08500F17457 /* photo-05.jpg */, 237 | FD9D1F71181FC08500F17457 /* photo-06.jpg */, 238 | FD9D1F72181FC08500F17457 /* photo-07.jpg */, 239 | FD9D1F73181FC08500F17457 /* photo-08.jpg */, 240 | FD9D1F74181FC08500F17457 /* photo-09.jpg */, 241 | FD9D1F75181FC08500F17457 /* photo-10.jpg */, 242 | FD9D1F76181FC08500F17457 /* photo-11.jpg */, 243 | FD9D1F77181FC08500F17457 /* photo-12.jpg */, 244 | FD9D1F78181FC08500F17457 /* photo-13.jpg */, 245 | FD9D1F79181FC08500F17457 /* photo-14.jpg */, 246 | FD9D1F7A181FC08500F17457 /* photo-15.jpg */, 247 | FD9D1F7B181FC08500F17457 /* photo-16.jpg */, 248 | FD9D1F7C181FC08500F17457 /* photo-17.jpg */, 249 | FD9D1F7D181FC08500F17457 /* photo-18.jpg */, 250 | FD9D1F7E181FC08500F17457 /* photo-19.jpg */, 251 | FD9D1F7F181FC08500F17457 /* photo-20.jpg */, 252 | FD9D1F80181FC08500F17457 /* photo-21.jpg */, 253 | FD9D1F81181FC08500F17457 /* photo-22.jpg */, 254 | FD9D1F82181FC08500F17457 /* photo-23.jpg */, 255 | FD9D1F83181FC08500F17457 /* photo-24.jpg */, 256 | ); 257 | path = Images; 258 | sourceTree = ""; 259 | }; 260 | FDC80708182B849F00ADF578 /* NHBalancedFlowLayout */ = { 261 | isa = PBXGroup; 262 | children = ( 263 | FDC80709182B84C600ADF578 /* NHBalancedFlowLayout.h */, 264 | FDC8070A182B84C600ADF578 /* NHBalancedFlowLayout.m */, 265 | FDC8070B182B84C600ADF578 /* NHLinearPartition.h */, 266 | FDC8070C182B84C600ADF578 /* NHLinearPartition.m */, 267 | ); 268 | name = NHBalancedFlowLayout; 269 | path = ../NHBalancedFlowLayout; 270 | sourceTree = ""; 271 | }; 272 | /* End PBXGroup section */ 273 | 274 | /* Begin PBXNativeTarget section */ 275 | 50CAF4C3180430A6009A7FA1 /* NHBalancedFlowLayoutDemo */ = { 276 | isa = PBXNativeTarget; 277 | buildConfigurationList = 50CAF4F6180430A6009A7FA1 /* Build configuration list for PBXNativeTarget "NHBalancedFlowLayoutDemo" */; 278 | buildPhases = ( 279 | 50CAF4C0180430A6009A7FA1 /* Sources */, 280 | 50CAF4C1180430A6009A7FA1 /* Frameworks */, 281 | 50CAF4C2180430A6009A7FA1 /* Resources */, 282 | ); 283 | buildRules = ( 284 | ); 285 | dependencies = ( 286 | ); 287 | name = NHBalancedFlowLayoutDemo; 288 | productName = LinearPartition; 289 | productReference = 50CAF4C4180430A6009A7FA1 /* NHBalancedFlowLayoutDemo.app */; 290 | productType = "com.apple.product-type.application"; 291 | }; 292 | 50CAF4E4180430A6009A7FA1 /* NHBalancedFlowLayoutDemoTests */ = { 293 | isa = PBXNativeTarget; 294 | buildConfigurationList = 50CAF4F9180430A6009A7FA1 /* Build configuration list for PBXNativeTarget "NHBalancedFlowLayoutDemoTests" */; 295 | buildPhases = ( 296 | 50CAF4E1180430A6009A7FA1 /* Sources */, 297 | 50CAF4E2180430A6009A7FA1 /* Frameworks */, 298 | 50CAF4E3180430A6009A7FA1 /* Resources */, 299 | ); 300 | buildRules = ( 301 | ); 302 | dependencies = ( 303 | 50CAF4EB180430A6009A7FA1 /* PBXTargetDependency */, 304 | ); 305 | name = NHBalancedFlowLayoutDemoTests; 306 | productName = LinearPartitionTests; 307 | productReference = 50CAF4E5180430A6009A7FA1 /* NHBalancedFlowLayoutDemoTests.xctest */; 308 | productType = "com.apple.product-type.bundle.unit-test"; 309 | }; 310 | /* End PBXNativeTarget section */ 311 | 312 | /* Begin PBXProject section */ 313 | 50CAF4BC180430A6009A7FA1 /* Project object */ = { 314 | isa = PBXProject; 315 | attributes = { 316 | LastUpgradeCheck = 0720; 317 | ORGANIZATIONNAME = "Niels de Hoog"; 318 | TargetAttributes = { 319 | 50CAF4E4180430A6009A7FA1 = { 320 | TestTargetID = 50CAF4C3180430A6009A7FA1; 321 | }; 322 | }; 323 | }; 324 | buildConfigurationList = 50CAF4BF180430A6009A7FA1 /* Build configuration list for PBXProject "NHBalancedFlowLayoutDemo" */; 325 | compatibilityVersion = "Xcode 3.2"; 326 | developmentRegion = English; 327 | hasScannedForEncodings = 0; 328 | knownRegions = ( 329 | en, 330 | Base, 331 | ); 332 | mainGroup = 50CAF4BB180430A6009A7FA1; 333 | productRefGroup = 50CAF4C5180430A6009A7FA1 /* Products */; 334 | projectDirPath = ""; 335 | projectRoot = ""; 336 | targets = ( 337 | 50CAF4C3180430A6009A7FA1 /* NHBalancedFlowLayoutDemo */, 338 | 50CAF4E4180430A6009A7FA1 /* NHBalancedFlowLayoutDemoTests */, 339 | ); 340 | }; 341 | /* End PBXProject section */ 342 | 343 | /* Begin PBXResourcesBuildPhase section */ 344 | 50CAF4C2180430A6009A7FA1 /* Resources */ = { 345 | isa = PBXResourcesBuildPhase; 346 | buildActionMask = 2147483647; 347 | files = ( 348 | FD9D1F93181FC08500F17457 /* photo-16.jpg in Resources */, 349 | FD607149181E4C8B00593765 /* Main.storyboard in Resources */, 350 | FD9D1F86181FC08500F17457 /* photo-03.jpg in Resources */, 351 | FD9D1F9A181FC08500F17457 /* photo-23.jpg in Resources */, 352 | 50CAF4E0180430A6009A7FA1 /* Images.xcassets in Resources */, 353 | FD9D1F8D181FC08500F17457 /* photo-10.jpg in Resources */, 354 | FD9D1F92181FC08500F17457 /* photo-15.jpg in Resources */, 355 | FD9D1F8A181FC08500F17457 /* photo-07.jpg in Resources */, 356 | 50CAF4D2180430A6009A7FA1 /* InfoPlist.strings in Resources */, 357 | FD9D1F90181FC08500F17457 /* photo-13.jpg in Resources */, 358 | FD9D1F89181FC08500F17457 /* photo-06.jpg in Resources */, 359 | FD9D1F8B181FC08500F17457 /* photo-08.jpg in Resources */, 360 | FD9D1F8C181FC08500F17457 /* photo-09.jpg in Resources */, 361 | FD9D1F9B181FC08500F17457 /* photo-24.jpg in Resources */, 362 | FD9D1F91181FC08500F17457 /* photo-14.jpg in Resources */, 363 | FD9D1F98181FC08500F17457 /* photo-21.jpg in Resources */, 364 | FD9D1F85181FC08500F17457 /* photo-02.jpg in Resources */, 365 | FD9D1F96181FC08500F17457 /* photo-19.jpg in Resources */, 366 | FD9D1F84181FC08500F17457 /* photo-01.jpg in Resources */, 367 | FD9D1F87181FC08500F17457 /* photo-04.jpg in Resources */, 368 | FD9D1F8E181FC08500F17457 /* photo-11.jpg in Resources */, 369 | FD9D1F8F181FC08500F17457 /* photo-12.jpg in Resources */, 370 | FD9D1F94181FC08500F17457 /* photo-17.jpg in Resources */, 371 | FD9D1F97181FC08500F17457 /* photo-20.jpg in Resources */, 372 | FD9D1F95181FC08500F17457 /* photo-18.jpg in Resources */, 373 | FD9D1F99181FC08500F17457 /* photo-22.jpg in Resources */, 374 | FD9D1F88181FC08500F17457 /* photo-05.jpg in Resources */, 375 | ); 376 | runOnlyForDeploymentPostprocessing = 0; 377 | }; 378 | 50CAF4E3180430A6009A7FA1 /* Resources */ = { 379 | isa = PBXResourcesBuildPhase; 380 | buildActionMask = 2147483647; 381 | files = ( 382 | 50CAF4F1180430A6009A7FA1 /* InfoPlist.strings in Resources */, 383 | ); 384 | runOnlyForDeploymentPostprocessing = 0; 385 | }; 386 | /* End PBXResourcesBuildPhase section */ 387 | 388 | /* Begin PBXSourcesBuildPhase section */ 389 | 50CAF4C0180430A6009A7FA1 /* Sources */ = { 390 | isa = PBXSourcesBuildPhase; 391 | buildActionMask = 2147483647; 392 | files = ( 393 | 50CAF4DE180430A6009A7FA1 /* ViewController.m in Sources */, 394 | 50CAF4D8180430A6009A7FA1 /* AppDelegate.m in Sources */, 395 | FDC8070E182B84C600ADF578 /* NHLinearPartition.m in Sources */, 396 | FD9D1F69181FBFC600F17457 /* ImageCell.m in Sources */, 397 | 50CAF4D4180430A6009A7FA1 /* main.m in Sources */, 398 | FDDD339B18218D6500314905 /* UIImage+Decompression.m in Sources */, 399 | FDC8070D182B84C600ADF578 /* NHBalancedFlowLayout.m in Sources */, 400 | ); 401 | runOnlyForDeploymentPostprocessing = 0; 402 | }; 403 | 50CAF4E1180430A6009A7FA1 /* Sources */ = { 404 | isa = PBXSourcesBuildPhase; 405 | buildActionMask = 2147483647; 406 | files = ( 407 | FDFDF5141851D25500922D68 /* LinearPartitionTests.m in Sources */, 408 | ); 409 | runOnlyForDeploymentPostprocessing = 0; 410 | }; 411 | /* End PBXSourcesBuildPhase section */ 412 | 413 | /* Begin PBXTargetDependency section */ 414 | 50CAF4EB180430A6009A7FA1 /* PBXTargetDependency */ = { 415 | isa = PBXTargetDependency; 416 | target = 50CAF4C3180430A6009A7FA1 /* NHBalancedFlowLayoutDemo */; 417 | targetProxy = 50CAF4EA180430A6009A7FA1 /* PBXContainerItemProxy */; 418 | }; 419 | /* End PBXTargetDependency section */ 420 | 421 | /* Begin PBXVariantGroup section */ 422 | 50CAF4D0180430A6009A7FA1 /* InfoPlist.strings */ = { 423 | isa = PBXVariantGroup; 424 | children = ( 425 | 50CAF4D1180430A6009A7FA1 /* en */, 426 | ); 427 | name = InfoPlist.strings; 428 | sourceTree = ""; 429 | }; 430 | 50CAF4EF180430A6009A7FA1 /* InfoPlist.strings */ = { 431 | isa = PBXVariantGroup; 432 | children = ( 433 | 50CAF4F0180430A6009A7FA1 /* en */, 434 | ); 435 | name = InfoPlist.strings; 436 | sourceTree = ""; 437 | }; 438 | /* End PBXVariantGroup section */ 439 | 440 | /* Begin XCBuildConfiguration section */ 441 | 50CAF4F4180430A6009A7FA1 /* Debug */ = { 442 | isa = XCBuildConfiguration; 443 | buildSettings = { 444 | ALWAYS_SEARCH_USER_PATHS = NO; 445 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 446 | CLANG_CXX_LIBRARY = "libc++"; 447 | CLANG_ENABLE_MODULES = YES; 448 | CLANG_ENABLE_OBJC_ARC = YES; 449 | CLANG_WARN_BOOL_CONVERSION = YES; 450 | CLANG_WARN_CONSTANT_CONVERSION = YES; 451 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 452 | CLANG_WARN_EMPTY_BODY = YES; 453 | CLANG_WARN_ENUM_CONVERSION = YES; 454 | CLANG_WARN_INT_CONVERSION = YES; 455 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 456 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 457 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 458 | COPY_PHASE_STRIP = NO; 459 | ENABLE_TESTABILITY = YES; 460 | GCC_C_LANGUAGE_STANDARD = gnu99; 461 | GCC_DYNAMIC_NO_PIC = NO; 462 | GCC_OPTIMIZATION_LEVEL = 0; 463 | GCC_PREPROCESSOR_DEFINITIONS = ( 464 | "DEBUG=1", 465 | "$(inherited)", 466 | ); 467 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 468 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 469 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 470 | GCC_WARN_UNDECLARED_SELECTOR = YES; 471 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 472 | GCC_WARN_UNUSED_FUNCTION = YES; 473 | GCC_WARN_UNUSED_VARIABLE = YES; 474 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 475 | ONLY_ACTIVE_ARCH = YES; 476 | SDKROOT = iphoneos; 477 | TARGETED_DEVICE_FAMILY = 2; 478 | }; 479 | name = Debug; 480 | }; 481 | 50CAF4F5180430A6009A7FA1 /* Release */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | ALWAYS_SEARCH_USER_PATHS = NO; 485 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 486 | CLANG_CXX_LIBRARY = "libc++"; 487 | CLANG_ENABLE_MODULES = YES; 488 | CLANG_ENABLE_OBJC_ARC = YES; 489 | CLANG_WARN_BOOL_CONVERSION = YES; 490 | CLANG_WARN_CONSTANT_CONVERSION = YES; 491 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 492 | CLANG_WARN_EMPTY_BODY = YES; 493 | CLANG_WARN_ENUM_CONVERSION = YES; 494 | CLANG_WARN_INT_CONVERSION = YES; 495 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 496 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 497 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 498 | COPY_PHASE_STRIP = YES; 499 | ENABLE_NS_ASSERTIONS = NO; 500 | GCC_C_LANGUAGE_STANDARD = gnu99; 501 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 502 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 503 | GCC_WARN_UNDECLARED_SELECTOR = YES; 504 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 505 | GCC_WARN_UNUSED_FUNCTION = YES; 506 | GCC_WARN_UNUSED_VARIABLE = YES; 507 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 508 | SDKROOT = iphoneos; 509 | TARGETED_DEVICE_FAMILY = 2; 510 | VALIDATE_PRODUCT = YES; 511 | }; 512 | name = Release; 513 | }; 514 | 50CAF4F7180430A6009A7FA1 /* Debug */ = { 515 | isa = XCBuildConfiguration; 516 | buildSettings = { 517 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 518 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 519 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 520 | GCC_PREFIX_HEADER = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Prefix.pch"; 521 | INFOPLIST_FILE = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist"; 522 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 523 | PRODUCT_BUNDLE_IDENTIFIER = "com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}"; 524 | PRODUCT_NAME = NHBalancedFlowLayoutDemo; 525 | TARGETED_DEVICE_FAMILY = "1,2"; 526 | WRAPPER_EXTENSION = app; 527 | }; 528 | name = Debug; 529 | }; 530 | 50CAF4F8180430A6009A7FA1 /* Release */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 534 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 535 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 536 | GCC_PREFIX_HEADER = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Prefix.pch"; 537 | INFOPLIST_FILE = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist"; 538 | IPHONEOS_DEPLOYMENT_TARGET = 6.0; 539 | PRODUCT_BUNDLE_IDENTIFIER = "com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}"; 540 | PRODUCT_NAME = NHBalancedFlowLayoutDemo; 541 | TARGETED_DEVICE_FAMILY = "1,2"; 542 | WRAPPER_EXTENSION = app; 543 | }; 544 | name = Release; 545 | }; 546 | 50CAF4FA180430A6009A7FA1 /* Debug */ = { 547 | isa = XCBuildConfiguration; 548 | buildSettings = { 549 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/NHBalancedFlowLayoutDemo.app/NHBalancedFlowLayoutDemo"; 550 | FRAMEWORK_SEARCH_PATHS = ( 551 | "$(SDKROOT)/Developer/Library/Frameworks", 552 | "$(inherited)", 553 | "$(DEVELOPER_FRAMEWORKS_DIR)", 554 | ); 555 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 556 | GCC_PREFIX_HEADER = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Prefix.pch"; 557 | GCC_PREPROCESSOR_DEFINITIONS = ( 558 | "DEBUG=1", 559 | "$(inherited)", 560 | ); 561 | INFOPLIST_FILE = "NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist"; 562 | PRODUCT_BUNDLE_IDENTIFIER = "com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}"; 563 | PRODUCT_NAME = NHBalancedFlowLayoutDemoTests; 564 | TEST_HOST = "$(BUNDLE_LOADER)"; 565 | WRAPPER_EXTENSION = xctest; 566 | }; 567 | name = Debug; 568 | }; 569 | 50CAF4FB180430A6009A7FA1 /* Release */ = { 570 | isa = XCBuildConfiguration; 571 | buildSettings = { 572 | BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/NHBalancedFlowLayoutDemo.app/NHBalancedFlowLayoutDemo"; 573 | FRAMEWORK_SEARCH_PATHS = ( 574 | "$(SDKROOT)/Developer/Library/Frameworks", 575 | "$(inherited)", 576 | "$(DEVELOPER_FRAMEWORKS_DIR)", 577 | ); 578 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 579 | GCC_PREFIX_HEADER = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Prefix.pch"; 580 | INFOPLIST_FILE = "NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist"; 581 | PRODUCT_BUNDLE_IDENTIFIER = "com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}"; 582 | PRODUCT_NAME = NHBalancedFlowLayoutDemoTests; 583 | TEST_HOST = "$(BUNDLE_LOADER)"; 584 | WRAPPER_EXTENSION = xctest; 585 | }; 586 | name = Release; 587 | }; 588 | /* End XCBuildConfiguration section */ 589 | 590 | /* Begin XCConfigurationList section */ 591 | 50CAF4BF180430A6009A7FA1 /* Build configuration list for PBXProject "NHBalancedFlowLayoutDemo" */ = { 592 | isa = XCConfigurationList; 593 | buildConfigurations = ( 594 | 50CAF4F4180430A6009A7FA1 /* Debug */, 595 | 50CAF4F5180430A6009A7FA1 /* Release */, 596 | ); 597 | defaultConfigurationIsVisible = 0; 598 | defaultConfigurationName = Release; 599 | }; 600 | 50CAF4F6180430A6009A7FA1 /* Build configuration list for PBXNativeTarget "NHBalancedFlowLayoutDemo" */ = { 601 | isa = XCConfigurationList; 602 | buildConfigurations = ( 603 | 50CAF4F7180430A6009A7FA1 /* Debug */, 604 | 50CAF4F8180430A6009A7FA1 /* Release */, 605 | ); 606 | defaultConfigurationIsVisible = 0; 607 | defaultConfigurationName = Release; 608 | }; 609 | 50CAF4F9180430A6009A7FA1 /* Build configuration list for PBXNativeTarget "NHBalancedFlowLayoutDemoTests" */ = { 610 | isa = XCConfigurationList; 611 | buildConfigurations = ( 612 | 50CAF4FA180430A6009A7FA1 /* Debug */, 613 | 50CAF4FB180430A6009A7FA1 /* Release */, 614 | ); 615 | defaultConfigurationIsVisible = 0; 616 | defaultConfigurationName = Release; 617 | }; 618 | /* End XCConfigurationList section */ 619 | }; 620 | rootObject = 50CAF4BC180430A6009A7FA1 /* Project object */; 621 | } 622 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 08-10-13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 08-10-13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "ViewController.h" 11 | 12 | @implementation AppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | return YES; 17 | } 18 | 19 | - (void)applicationWillResignActive:(UIApplication *)application 20 | { 21 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 22 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 23 | } 24 | 25 | - (void)applicationDidEnterBackground:(UIApplication *)application 26 | { 27 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 28 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 29 | } 30 | 31 | - (void)applicationWillEnterForeground:(UIApplication *)application 32 | { 33 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | - (void)applicationDidBecomeActive:(UIApplication *)application 37 | { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application 42 | { 43 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/ImageCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // OrangeCell.h 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 29/10/13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ImageCell : UICollectionViewCell 12 | 13 | @property (nonatomic, strong) IBOutlet UIImageView *imageView; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/ImageCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // OrangeCell.m 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 29/10/13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import "ImageCell.h" 10 | 11 | @implementation ImageCell 12 | 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "ipad", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "ipad", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "ipad", 15 | "size" : "40x40", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "40x40", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "76x76", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "76x76", 31 | "scale" : "2x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "ipad", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "7.0", 8 | "scale" : "1x" 9 | }, 10 | { 11 | "orientation" : "landscape", 12 | "idiom" : "ipad", 13 | "extent" : "full-screen", 14 | "minimum-system-version" : "7.0", 15 | "scale" : "1x" 16 | }, 17 | { 18 | "orientation" : "portrait", 19 | "idiom" : "ipad", 20 | "extent" : "full-screen", 21 | "minimum-system-version" : "7.0", 22 | "scale" : "2x" 23 | }, 24 | { 25 | "orientation" : "landscape", 26 | "idiom" : "ipad", 27 | "extent" : "full-screen", 28 | "minimum-system-version" : "7.0", 29 | "scale" : "2x" 30 | } 31 | ], 32 | "info" : { 33 | "version" : 1, 34 | "author" : "xcode" 35 | } 36 | } -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every source file. 5 | // 6 | 7 | #import 8 | 9 | #ifndef __IPHONE_5_0 10 | #warning "This project uses features only available in iOS SDK 5.0 and later." 11 | #endif 12 | 13 | #ifdef __OBJC__ 14 | #import 15 | #import 16 | #endif 17 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/UIImage+Decompression.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Decompression.h 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 30/10/13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * Decompresses an image so it can be displayed without lag. 13 | * Based on: http://ioscodesnippet.com/2011/10/02/force-decompressing-uiimage-in-background-to-achieve/ 14 | */ 15 | @interface UIImage (Decompression) 16 | 17 | + (UIImage *)decodedImageWithImage:(UIImage *)image; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/UIImage+Decompression.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Decompression.m 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 30/10/13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import "UIImage+Decompression.h" 10 | 11 | @implementation UIImage (Decompression) 12 | 13 | + (UIImage *)decodedImageWithImage:(UIImage *)image 14 | { 15 | CGImageRef imageRef = image.CGImage; 16 | // System only supports RGB, set explicitly and prevent context error 17 | // if the downloaded image is not the supported format 18 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 19 | 20 | CGContextRef context = CGBitmapContextCreate(NULL, 21 | CGImageGetWidth(imageRef), 22 | CGImageGetHeight(imageRef), 23 | 8, 24 | // width * 4 will be enough because are in ARGB format, don't read from the image 25 | CGImageGetWidth(imageRef) * 4, 26 | colorSpace, 27 | // kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little 28 | // makes system don't need to do extra conversion when displayed. 29 | kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little); 30 | CGColorSpaceRelease(colorSpace); 31 | 32 | if ( ! context) { 33 | return nil; 34 | } 35 | 36 | CGRect rect = (CGRect){CGPointZero, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef)}; 37 | CGContextDrawImage(context, rect, imageRef); 38 | CGImageRef decompressedImageRef = CGBitmapContextCreateImage(context); 39 | CGContextRelease(context); 40 | 41 | UIImage *decompressedImage = [[UIImage alloc] initWithCGImage:decompressedImageRef]; 42 | CGImageRelease(decompressedImageRef); 43 | return decompressedImage; 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 08-10-13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UICollectionViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 08-10-13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "ImageCell.h" 11 | #import "NHLinearPartition.h" 12 | #import "UIImage+Decompression.h" 13 | #import "NHBalancedFlowLayout.h" 14 | 15 | #define NUMBER_OF_IMAGES 24 16 | 17 | #define HEADER_SIZE 100.0f 18 | #define FOOTER_SIZE 100.0f 19 | 20 | @interface ViewController () 21 | 22 | @property (nonatomic, strong) NSArray *images; 23 | 24 | @end 25 | 26 | @implementation ViewController 27 | 28 | - (id)initWithCoder:(NSCoder *)aDecoder 29 | { 30 | self = [super initWithCoder:aDecoder]; 31 | if (self) { 32 | 33 | NSMutableArray *images = [[NSMutableArray alloc] init]; 34 | for (int i = 1; i <= NUMBER_OF_IMAGES; i++) { 35 | NSString *imageName = [NSString stringWithFormat:@"photo-%02d.jpg", i]; 36 | [images addObject:[UIImage imageNamed:imageName]]; 37 | } 38 | _images = [images copy]; 39 | } 40 | 41 | return self; 42 | } 43 | 44 | - (void)viewDidLoad 45 | { 46 | [super viewDidLoad]; 47 | 48 | NHBalancedFlowLayout *layout = (NHBalancedFlowLayout *)self.collectionViewLayout; 49 | layout.headerReferenceSize = CGSizeMake(HEADER_SIZE, HEADER_SIZE); 50 | layout.footerReferenceSize = CGSizeMake(FOOTER_SIZE, FOOTER_SIZE); 51 | } 52 | 53 | #pragma mark - UICollectionViewFlowLayoutDelegate 54 | 55 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(NHBalancedFlowLayout *)collectionViewLayout preferredSizeForItemAtIndexPath:(NSIndexPath *)indexPath 56 | { 57 | return [[self.images objectAtIndex:indexPath.item] size]; 58 | } 59 | 60 | #pragma mark - UICollectionView data source 61 | 62 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 63 | { 64 | return 1; 65 | } 66 | 67 | - (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section; 68 | { 69 | return [self.images count]; 70 | } 71 | 72 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath; 73 | { 74 | ImageCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"ImageCell" forIndexPath:indexPath]; 75 | cell.imageView.image = nil; 76 | 77 | /** 78 | * Decompress image on background thread before displaying it to prevent lag 79 | */ 80 | NSInteger rowIndex = indexPath.row; 81 | dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 82 | 83 | UIImage *image = [UIImage decodedImageWithImage:[self.images objectAtIndex:indexPath.item]]; 84 | 85 | dispatch_async(dispatch_get_main_queue(), ^{ 86 | NSIndexPath *currentIndexPathForCell = [collectionView indexPathForCell:cell]; 87 | if (currentIndexPathForCell.row == rowIndex) { 88 | cell.imageView.image = image; 89 | } 90 | }); 91 | }); 92 | 93 | return cell; 94 | } 95 | 96 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 97 | { 98 | UICollectionReusableView *view = nil; 99 | 100 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 101 | view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"HeaderView" forIndexPath:indexPath]; 102 | } 103 | else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 104 | view = [collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"FooterView" forIndexPath:indexPath]; 105 | } 106 | 107 | return view; 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // BalancedFlowLayoutDemo 4 | // 5 | // Created by Niels de Hoog on 08-10-13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "AppDelegate.h" 12 | 13 | int main(int argc, char * argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | ${EXECUTABLE_NAME} 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemoTests/LinearPartitionTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // LinearPartitionTests.m 3 | // LinearPartitionTests 4 | // 5 | // Created by Niels de Hoog on 08-10-13. 6 | // Copyright (c) 2013 Niels de Hoog. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NHLinearPartition.h" 11 | 12 | @interface LinearPartitionTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation LinearPartitionTests 17 | 18 | - (void)setUp 19 | { 20 | [super setUp]; 21 | // Put setup code here. This method is called before the invocation of each test method in the class. 22 | } 23 | 24 | - (void)tearDown 25 | { 26 | // Put teardown code here. This method is called after the invocation of each test method in the class. 27 | [super tearDown]; 28 | } 29 | 30 | - (void)testNumberOfPartitionsEqualToSequenceCount 31 | { 32 | NSArray *sequence = @[@346, @146, @125]; 33 | NSInteger numberOfPartitions = 3; 34 | 35 | NSArray *partition = [NHLinearPartition linearPartitionForSequence:sequence numberOfPartitions:numberOfPartitions]; 36 | XCTAssertNotNil(partition, @"should return valid partition"); 37 | XCTAssert([partition count] == numberOfPartitions, @"should contain 3 objects"); 38 | } 39 | 40 | - (void)testOutOfBoundsException 41 | { 42 | NSArray *sequence = @[@346, @150, @125, @71, @137]; 43 | NSInteger numberOfPartitions = 4; 44 | 45 | NSArray *partition = [NHLinearPartition linearPartitionForSequence:sequence numberOfPartitions:numberOfPartitions]; 46 | XCTAssertNotNil(partition, @"should return valid partition"); 47 | XCTAssert([partition count] == numberOfPartitions, @"should contain 4 objects"); 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemoTests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-01.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-02.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-03.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-04.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-05.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-06.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-07.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-08.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-09.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-10.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-11.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-12.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-13.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-14.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-15.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-16.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-17.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-18.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-19.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-20.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-21.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-22.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-23.jpg -------------------------------------------------------------------------------- /NHBalancedFlowLayoutDemo/Resources/Images/photo-24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/graetzer/NHBalancedFlowLayout/315d5ae4d64bf267d1350c091cf98170dfd98d7a/NHBalancedFlowLayoutDemo/Resources/Images/photo-24.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NHBalancedFlowLayout 2 | ================== 3 | 4 | UICollectionViewLayout subclass for displaying items of different sizes in a grid without wasting any visual space. Inspired by: http://www.crispymtn.com/stories/the-algorithm-for-a-perfectly-balanced-photo-gallery 5 | 6 | 7 | ## About this Fork 8 | 9 | Mainly three differences to the original: 10 | 11 | - It fixes the [Issue of upscaled images](https://github.com/njdehoog/NHBalancedFlowLayout/issues/22) wich occurs if the partition algorithm assigns a single item to one row. 12 | - Incorporates a fix for a crash on `[UICollectionView insertSections:]` from [Pull Request #24](https://github.com/njdehoog/NHBalancedFlowLayout/pull/24) 13 | - It implements swipe in and out animations, when inserting or removing cells. 14 | 15 | 16 | Hopefully this can be useful for some of you, if you don't want the animations you can easily remove them in the code: [piece 1](https://github.com/graetzer/NHBalancedFlowLayout/blob/master/NHBalancedFlowLayout/NHBalancedFlowLayout.m#L267) and [piece 2](https://github.com/graetzer/NHBalancedFlowLayout/blob/master/NHBalancedFlowLayout/NHBalancedFlowLayout.m#L289). 17 | 18 | ## Notes 19 | * Tested with iOS 7, but should be compatible with iOS6 as well 20 | * Works with iPhone and iPad 21 | * All interface orientations are supported 22 | 23 | ## Screenshots 24 | 25 | 26 | 27 | 28 | ## Installation 29 | 30 | The easiest way is to use CocoaPods. If you don't already, here's a [guide](http://guides.cocoapods.org/using/getting-started.html). 31 | 32 | pod 'NHBalancedFlowLayout', '~> 0.2' 33 | 34 | If you don't use CocoaPods, you'll need to copy the following files into your project: 35 | 36 | * NHBalancedFlowLayout.h 37 | * NHBalancedFlowLayout.m 38 | * NHLinearPartition.h 39 | * NHLinearPartition.m 40 | 41 | ## Credits 42 | 43 | Attributions for the photos in the same order as they appear in the demo: 44 | 45 | * http://www.flickr.com/photos/adriensifre/7162196453/ 46 | * http://www.flickr.com/photos/ucumari/2949701552/ 47 | * http://www.flickr.com/photos/ucumari/388370102/ 48 | * http://www.flickr.com/photos/38659937@N06/3567549164/ 49 | * http://www.flickr.com/photos/vinothchandar/5139245960/ 50 | * http://www.flickr.com/photos/expressmonorail/3581442376/ 51 | * http://www.flickr.com/photos/keithmwilliams/4465380932/ 52 | * http://en.wikipedia.org/wiki/File:Bobcat-Texas-9110.jpg 53 | * http://www.flickr.com/photos/thecaucas/2390806406/ 54 | * http://www.flickr.com/photos/ucumari/2342703987/ 55 | * http://www.flickr.com/photos/ucumari/2317386162/ 56 | * http://www.flickr.com/photos/ucumari/448815607/ 57 | * http://www.flickr.com/photos/kshathriya/331847679/ 58 | * http://www.flickr.com/photos/ucumari/367041320/ 59 | * http://www.flickr.com/photos/da100fotos/465868860/ 60 | * http://www.flickr.com/photos/hhoyer/3758550410/ 61 | * http://www.flickr.com/photos/tambako/9314344697/ 62 | * http://www.flickr.com/photos/sergiu_bacioiu/4485931477/ 63 | * http://www.flickr.com/photos/matthileo/4077650329/ 64 | * http://www.flickr.com/photos/markop/491027707/ 65 | * http://www.flickr.com/photos/stevedave/3566325269/ 66 | * http://www.flickr.com/photos/genista/127048347/ 67 | * http://www.flickr.com/photos/wagner-machado-carlos-lemes/5540332691/ 68 | * http://www.flickr.com/photos/29487767@N02/3438177701/ 69 | --------------------------------------------------------------------------------