Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified NHBalancedFlowLayout/NHBalancedFlowLayout.h
100644 → 100755
Empty file.
128 changes: 116 additions & 12 deletions NHBalancedFlowLayout/NHBalancedFlowLayout.m
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ @interface NHBalancedFlowLayout ()
{
CGRect **_itemFrameSections;
NSInteger _numberOfItemFrameSections;


NSMutableArray *_deleteIndexPaths, *_insertIndexPaths;
CGFloat centerXOffset;
}

@property (nonatomic) CGSize contentSize;
Expand Down Expand Up @@ -97,7 +101,7 @@ - (void)prepareLayout

NSMutableArray *headerFrames = [NSMutableArray array];
NSMutableArray *footerFrames = [NSMutableArray array];

CGSize contentSize = CGSizeZero;

// first release old item frame sections
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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);
}
Expand All @@ -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
Expand All @@ -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)) {
Expand All @@ -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];
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion NHBalancedFlowLayout/NHLinearPartition.h
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,6 @@
*/
@interface NHLinearPartition : NSObject

+ (NSArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions;
+ (NSMutableArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(NSInteger)numberOfPartitions;

@end
10 changes: 5 additions & 5 deletions NHBalancedFlowLayout/NHLinearPartition.m
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -128,7 +128,7 @@ + (NSArray *)linearPartitionForSequence:(NSArray *)sequence numberOfPartitions:(

[answer insertObject:currentAnswer atIndex:0];

return [answer copy];
return answer;
}

@end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@
50CAF4BC180430A6009A7FA1 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0500;
LastUpgradeCheck = 0720;
ORGANIZATIONNAME = "Niels de Hoog";
TargetAttributes = {
50CAF4E4180430A6009A7FA1 = {
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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",
Expand All @@ -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;
Expand All @@ -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",
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>com.invisiblepixel.${PRODUCT_NAME:rfc1034identifier}</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down