Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixing crash and Issue 9 #39

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
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
@@ -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);
2 changes: 1 addition & 1 deletion NHBalancedFlowLayout/NHLinearPartition.h
100644 → 100755
Original file line number Diff line number Diff line change
@@ -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
@@ -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
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
@@ -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>
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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