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