diff --git a/NHBalancedFlowLayout/NHBalancedFlowLayout.h b/NHBalancedFlowLayout/NHBalancedFlowLayout.h old mode 100644 new mode 100755 diff --git a/NHBalancedFlowLayout/NHBalancedFlowLayout.m b/NHBalancedFlowLayout/NHBalancedFlowLayout.m old mode 100644 new mode 100755 index aa08265..3616c91 --- a/NHBalancedFlowLayout/NHBalancedFlowLayout.m +++ b/NHBalancedFlowLayout/NHBalancedFlowLayout.m @@ -13,6 +13,10 @@ @interface NHBalancedFlowLayout () { CGRect **_itemFrameSections; NSInteger _numberOfItemFrameSections; + + + NSMutableArray *_deleteIndexPaths, *_insertIndexPaths; + CGFloat centerXOffset; } @property (nonatomic) CGSize contentSize; @@ -97,7 +101,7 @@ - (void)prepareLayout NSMutableArray *headerFrames = [NSMutableArray array]; NSMutableArray *footerFrames = [NSMutableArray array]; - + CGSize contentSize = CGSizeZero; // first release old item frame sections @@ -107,7 +111,7 @@ - (void)prepareLayout _numberOfItemFrameSections = [self.collectionView numberOfSections]; _itemFrameSections = (CGRect **)malloc(sizeof(CGRect *) * _numberOfItemFrameSections); - for (int section = 0; section < [self.collectionView numberOfSections]; section++) { + for (int section = 0; section < _numberOfItemFrameSections; section++) { // add new item frames array to sections array NSInteger numberOfItemsInSections = [self.collectionView numberOfItemsInSection:section]; CGRect *itemFrames = (CGRect *)malloc(sizeof(CGRect) * numberOfItemsInSections); @@ -126,7 +130,7 @@ - (void)prepareLayout CGFloat totalItemSize = [self totalItemSizeForSection:section preferredRowSize:idealHeight]; NSInteger numberOfRows = MAX(roundf(totalItemSize / [self viewPortAvailableSize]), 1); - + CGPoint sectionOffset; if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { sectionOffset = CGPointMake(0, contentSize.height + headerSize.height); @@ -144,7 +148,7 @@ - (void)prepareLayout footerFrame = CGRectMake(contentSize.width + headerSize.width + sectionSize.width, 0, footerSize.width, CGRectGetHeight(self.collectionView.bounds)); } [footerFrames addObject:[NSValue valueWithCGRect:footerFrame]]; - + if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { contentSize = CGSizeMake(sectionSize.width, contentSize.height + headerSize.height + sectionSize.height + footerSize.height); } @@ -157,6 +161,31 @@ - (void)prepareLayout self.footerFrames = [footerFrames copy]; self.contentSize = contentSize; + CGSize size = self.collectionView.frame.size; + centerXOffset = 2* size.width; +} + +- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems { + // Keep track of insert and delete index paths + [super prepareForCollectionViewUpdates:updateItems]; + + _deleteIndexPaths = [NSMutableArray array]; + _insertIndexPaths = [NSMutableArray array]; + + for (UICollectionViewUpdateItem *update in updateItems) { + if (update.updateAction == UICollectionUpdateActionDelete) { + [_deleteIndexPaths addObject:update.indexPathBeforeUpdate]; + } else if (update.updateAction == UICollectionUpdateActionInsert) { + [_insertIndexPaths addObject:update.indexPathAfterUpdate]; + } + } +} + +- (void)finalizeCollectionViewUpdates { + [super finalizeCollectionViewUpdates]; + // release the insert and delete index paths + _deleteIndexPaths = nil; + _insertIndexPaths = nil; } - (CGSize)collectionViewContentSize @@ -167,17 +196,19 @@ - (CGSize)collectionViewContentSize - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { NSMutableArray *layoutAttributes = [NSMutableArray array]; + NSInteger n = [self.collectionView numberOfSections]; - for (NSInteger section = 0, n = [self.collectionView numberOfSections]; section < n; section++) { + for (NSInteger section = 0; section < n; section++) { NSIndexPath *sectionIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; - + UICollectionViewLayoutAttributes *headerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:sectionIndexPath]; - if (! CGSizeEqualToSize(headerAttributes.frame.size, CGSizeZero) && CGRectIntersectsRect(headerAttributes.frame, rect)) { + CGSize size = headerAttributes.frame.size; + if (size.height != 0 && size.width != 0 && CGRectIntersectsRect(headerAttributes.frame, rect)) { [layoutAttributes addObject:headerAttributes]; } - + for (int i = 0; i < [self.collectionView numberOfItemsInSection:section]; i++) { CGRect itemFrame = _itemFrameSections[section][i]; if (CGRectIntersectsRect(rect, itemFrame)) { @@ -188,8 +219,8 @@ - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect UICollectionViewLayoutAttributes *footerAttributes = [self layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:sectionIndexPath]; - - if (! CGSizeEqualToSize(footerAttributes.frame.size, CGSizeZero) && CGRectIntersectsRect(footerAttributes.frame, rect)) { + size = footerAttributes.frame.size; + if (size.width != 0 && size.height != 0 && CGRectIntersectsRect(footerAttributes.frame, rect)) { [layoutAttributes addObject:footerAttributes]; } } @@ -233,6 +264,50 @@ - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds return NO; } +- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath:(NSIndexPath *)itemIndexPath +{ + // Must call super + UICollectionViewLayoutAttributes *attributes = [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; + + if ([_insertIndexPaths containsObject:itemIndexPath]) { + // only change attributes on inserted cells + if (!attributes) + attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; + + // Configure attributes ... + attributes.alpha = 0.5; + CGPoint center = attributes.center; + attributes.center = CGPointMake(center.x+centerXOffset, center.y); + } + + return attributes; +} + +// Note: name of method changed +// Also this gets called for all visible cells (not just the deleted ones) and +// even gets called when inserting cells! +- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath:(NSIndexPath *)itemIndexPath +{ + // So far, calling super hasn't been strictly necessary here, but leaving it in + // for good measure + UICollectionViewLayoutAttributes *attributes = [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; + + if ([_deleteIndexPaths containsObject:itemIndexPath]) + { + // only change attributes on deleted cells + if (!attributes) + attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath]; + + // Configure attributes ... + attributes.alpha = 0.5; + CGPoint center = attributes.center; + attributes.center = CGPointMake(center.x-centerXOffset, center.y); + //attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0); + } + + return attributes; +} + #pragma mark - Layout helpers - (CGRect)headerFrameForSection:(NSInteger)section @@ -253,7 +328,9 @@ - (CGRect)footerFrameForSection:(NSInteger)section - (CGFloat)totalItemSizeForSection:(NSInteger)section preferredRowSize:(CGFloat)preferredRowSize { CGFloat totalItemSize = 0; - for (NSInteger i = 0, n = [self.collectionView numberOfItemsInSection:section]; i < n; i++) { + NSUInteger n = [self.collectionView.dataSource collectionView:self.collectionView numberOfItemsInSection:section]; + + for (NSInteger i = 0; i < n; i++) { CGSize preferredSize = [self.delegate collectionView:self.collectionView layout:self preferredSizeForItemAtIndexPath:[NSIndexPath indexPathForItem:i inSection:section]]; if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { @@ -282,7 +359,34 @@ - (NSArray *)weightsForItemsInSection:(NSInteger)section - (void)setFrames:(CGRect *)frames forItemsInSection:(NSInteger)section numberOfRows:(NSUInteger)numberOfRows sectionOffset:(CGPoint)sectionOffset sectionSize:(CGSize *)sectionSize { NSArray *weights = [self weightsForItemsInSection:section]; - NSArray *partition = [NHLinearPartition linearPartitionForSequence:weights numberOfPartitions:numberOfRows]; + + if (weights.count == 0) { + *sectionSize = CGSizeZero; + return; + } + + NSMutableArray *partition = [NHLinearPartition linearPartitionForSequence:weights numberOfPartitions:numberOfRows]; + + // workaround to remove single images in a row + for (NSInteger i = 0; i < partition.count; i++) { + NSArray *row = partition[i]; + if (row.count == 1) { + NSArray *prev = i > 0 ? partition[i-1] : nil; + NSArray *next = i < partition.count-1 ? partition[i+1] : nil; + if (prev || next) { + // stick the image in the row with less images in it + if (next == nil || (prev != nil && prev.count < next.count)) { + partition[i-1] = [prev arrayByAddingObject:row[0]]; + } else { + NSMutableArray *arr = [next mutableCopy]; + [arr insertObject:row[0] atIndex:0]; + partition[i+1] = arr; + } + [partition removeObjectAtIndex:i]; + i--; + } + } + } int i = 0; CGPoint offset = CGPointMake(sectionOffset.x + self.sectionInset.left, sectionOffset.y + self.sectionInset.top); diff --git a/NHBalancedFlowLayout/NHLinearPartition.h b/NHBalancedFlowLayout/NHLinearPartition.h old mode 100644 new mode 100755 index e39187c..0b04b08 --- a/NHBalancedFlowLayout/NHLinearPartition.h +++ b/NHBalancedFlowLayout/NHLinearPartition.h @@ -15,6 +15,6 @@ */ @interface NHLinearPartition : NSObject -+ (NSArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions; ++ (NSMutableArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions; @end diff --git a/NHBalancedFlowLayout/NHLinearPartition.m b/NHBalancedFlowLayout/NHLinearPartition.m old mode 100644 new mode 100755 index f9b5ea5..269abf2 --- a/NHBalancedFlowLayout/NHLinearPartition.m +++ b/NHBalancedFlowLayout/NHLinearPartition.m @@ -75,23 +75,23 @@ + (NSInteger *)linearPartitionTable:(NSArray *)sequence numPartitions:(NSInteger return solution; } -+ (NSArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions ++ (NSMutableArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions { NSInteger n = [sequence count]; NSInteger k = numberOfPartitions; - if (k <= 0) return @[]; + if (k <= 0) return [NSMutableArray array]; if (k >= n) { NSMutableArray *partition = [[NSMutableArray alloc] init]; [sequence enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { [partition addObject:@[obj]]; }]; - return [partition copy]; + return [partition mutableCopy]; } if (n == 1) { - return @[sequence]; + return [NSMutableArray arrayWithObject:sequence]; } // get the solution table @@ -128,7 +128,7 @@ + (NSArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:( [answer insertObject:currentAnswer atIndex:0]; - return [answer copy]; + return answer; } @end diff --git a/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo.xcodeproj/project.pbxproj b/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo.xcodeproj/project.pbxproj index d25a655..b8eeaf2 100644 --- a/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo.xcodeproj/project.pbxproj +++ b/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo.xcodeproj/project.pbxproj @@ -313,7 +313,7 @@ 50CAF4BC180430A6009A7FA1 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0500; + LastUpgradeCheck = 0720; ORGANIZATIONNAME = "Niels de Hoog"; TargetAttributes = { 50CAF4E4180430A6009A7FA1 = { @@ -442,7 +442,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -457,6 +456,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -482,7 +482,6 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; @@ -521,6 +520,7 @@ GCC_PREFIX_HEADER = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Prefix.pch"; INFOPLIST_FILE = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 6.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = NHBalancedFlowLayoutDemo; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; @@ -536,6 +536,7 @@ GCC_PREFIX_HEADER = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Prefix.pch"; INFOPLIST_FILE = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 6.0; + PRODUCT_BUNDLE_IDENTIFIER = "com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = NHBalancedFlowLayoutDemo; TARGETED_DEVICE_FAMILY = "1,2"; WRAPPER_EXTENSION = app; @@ -545,7 +546,6 @@ 50CAF4FA180430A6009A7FA1 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/NHBalancedFlowLayoutDemo.app/NHBalancedFlowLayoutDemo"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", @@ -559,6 +559,7 @@ "$(inherited)", ); INFOPLIST_FILE = "NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = NHBalancedFlowLayoutDemoTests; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; @@ -568,7 +569,6 @@ 50CAF4FB180430A6009A7FA1 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = "$(ARCHS_STANDARD_INCLUDING_64_BIT)"; BUNDLE_LOADER = "$(BUILT_PRODUCTS_DIR)/NHBalancedFlowLayoutDemo.app/NHBalancedFlowLayoutDemo"; FRAMEWORK_SEARCH_PATHS = ( "$(SDKROOT)/Developer/Library/Frameworks", @@ -578,6 +578,7 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Prefix.pch"; INFOPLIST_FILE = "NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist"; + PRODUCT_BUNDLE_IDENTIFIER = "com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}"; PRODUCT_NAME = NHBalancedFlowLayoutDemoTests; TEST_HOST = "$(BUNDLE_LOADER)"; WRAPPER_EXTENSION = xctest; diff --git a/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist b/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist index 21ea58d..b06b79c 100644 --- a/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist +++ b/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemo-Info.plist @@ -9,7 +9,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist b/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist index 9a051d8..169b6f7 100644 --- a/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist +++ b/NHBalancedFlowLayoutDemo/NHBalancedFlowLayoutDemoTests/BalancedFlowLayoutDemoTests-Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier - com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType diff --git a/README.md b/README.md index 65316a8..dde3dbd 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,18 @@ NHBalancedFlowLayout 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 + +## About this Fork + +Mainly three differences to the original: + +- 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. +- Incorporates a fix for a crash on `[UICollectionView insertSections:]` from [Pull Request #24](https://github.com/njdehoog/NHBalancedFlowLayout/pull/24) +- It implements swipe in and out animations, when inserting or removing cells. + + +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). + ## Notes * Tested with iOS 7, but should be compatible with iOS6 as well * Works with iPhone and iPad