diff --git a/Classes/AQGridView.h b/Classes/AQGridView.h index def30b7..702b52a 100755 --- a/Classes/AQGridView.h +++ b/Classes/AQGridView.h @@ -130,6 +130,7 @@ extern NSString * const AQGridViewSelectionDidChangeNotification; UIView * _headerView; UIView * _footerView; + struct { unsigned resizesCellWidths:1; @@ -138,7 +139,10 @@ extern NSString * const AQGridViewSelectionDidChangeNotification; unsigned allowsSelection:1; unsigned backgroundViewExtendsUp:1; unsigned backgroundViewExtendsDown:1; + unsigned backgroundViewExtendsLeft:1; + unsigned backgroundViewExtendsRight:1; unsigned usesPagedHorizontalScrolling:1; + unsigned layoutChange:1; unsigned updating:1; // unused unsigned ignoreTouchSelect:1; unsigned needsReload:1; @@ -230,7 +234,10 @@ extern NSString * const AQGridViewSelectionDidChangeNotification; @property (nonatomic, retain) UIView * backgroundView; // specifies a view to place behind the cells @property (nonatomic) BOOL backgroundViewExtendsUp; // default is NO. If YES, the background view extends upward and is visible during a bounce. @property (nonatomic) BOOL backgroundViewExtendsDown; // default is NO. If YES, the background view extends downward and is visible during a bounce. +@property (nonatomic) BOOL backgroundViewExtendsLeft; // default is NO. If YES, the background view extends left and is visible during a bounce. +@property (nonatomic) BOOL backgroundViewExtendsRight; // default is NO. If YES, the background view extends right and is visible during a bounce. @property (nonatomic) BOOL usesPagedHorizontalScrolling; // default is NO, and scrolls verticalls only. Set to YES to have horizontal-only scrolling by page. +@property (nonatomic) BOOL layoutChange; @property (nonatomic) AQGridViewCellSeparatorStyle separatorStyle; // default is AQGridViewCellSeparatorStyleEmptySpace @property (nonatomic, retain) UIColor * separatorColor; // ignored unless separatorStyle == AQGridViewCellSeparatorStyleSingleLine. Default is standard separator gray. diff --git a/Classes/AQGridView.m b/Classes/AQGridView.m index 7ca3834..532317c 100755 --- a/Classes/AQGridView.m +++ b/Classes/AQGridView.m @@ -112,6 +112,7 @@ - (void) _sharedGridViewInit self.canCancelContentTouches = YES; _selectedIndex = NSNotFound; + _flags.layoutChange = NO; _pendingSelectionIndex = NSNotFound; _flags.resizesCellWidths = 0; @@ -120,6 +121,7 @@ - (void) _sharedGridViewInit _flags.allowsSelection = 1; _flags.usesPagedHorizontalScrolling = NO; _flags.contentSizeFillsBounds = 1; + } - (id)initWithFrame: (CGRect) frame @@ -200,6 +202,8 @@ - (AQGridViewLayoutDirection) layoutDirection - (void) setLayoutDirection: (AQGridViewLayoutDirection) direction { _gridData.layoutDirection = direction; + _flags.layoutChange = YES; + } - (NSUInteger) numberOfItems @@ -249,6 +253,27 @@ - (void) setBackgroundViewExtendsUp: (BOOL) value _flags.backgroundViewExtendsUp = (value ? 1 : 0); } +- (BOOL) backgroundViewExtendsLeft +{ + return ( _flags.backgroundViewExtendsLeft); +} + +- (void) setBackgroundViewExtendsLeft: (BOOL) value +{ + _flags.backgroundViewExtendsLeft = (value ? 1 : 0); +} + + +- (BOOL) backgroundViewExtendsRight +{ + return ( _flags.backgroundViewExtendsRight); +} + +- (void) setBackgroundViewExtendsRight: (BOOL) value +{ + _flags.backgroundViewExtendsRight = (value ? 1 : 0); +} + - (BOOL) requiresSelection { return ( _flags.requiresSelection ); @@ -259,6 +284,16 @@ - (void) setRequiresSelection: (BOOL) value _flags.requiresSelection = (value ? 1 : 0); } +- (BOOL) layoutChange +{ + return ( _flags.layoutChange ); +} + +- (void) setLayoutChange: (BOOL) value +{ + _flags.layoutChange = (value ? 1 : 0); +} + - (BOOL) resizesCellWidthToFit { return ( _flags.resizesCellWidths ); @@ -510,6 +545,8 @@ - (void) setContentSize: (CGSize) newSize { if ( (_flags.contentSizeFillsBounds == 1) && (newSize.height < self.bounds.size.height) ) newSize.height = self.bounds.size.height; + if ( (_flags.contentSizeFillsBounds == 1) && (newSize.width < self.bounds.size.width) ) + newSize.width = self.bounds.size.width; if (self.gridFooterView) { @@ -522,9 +559,12 @@ - (void) setContentSize: (CGSize) newSize if (newSize.height < footerHeight + minimumHeight) newSize.height = minimumHeight; } - - newSize.height = fmax(newSize.height, self.frame.size.height+1); - + if(self.layoutDirection == AQGridViewLayoutDirectionVertical){ + newSize.height = fmax(newSize.height, self.frame.size.height+1); + }else{ + newSize.width = fmax(newSize.width, self.frame.size.width+1); + + } CGSize oldSize = self.contentSize; [super setContentSize: newSize]; @@ -558,6 +598,10 @@ - (void) setBounds: (CGRect) bounds if ( !CGSizeEqualToSize(bounds.size, oldBounds.size) ) [self handleGridViewBoundsChanged: oldBounds toNewBounds: bounds]; + if ( _flags.layoutChange == YES ){ + [self handleGridViewBoundsChanged: oldBounds toNewBounds: bounds]; + _flags.layoutChange = NO; + } } - (BOOL) isEditing @@ -667,40 +711,77 @@ - (void) layoutSubviews } CGRect rect = CGRectZero; - rect.size.width = self.bounds.size.width; - rect.size.height = self.contentSize.height - (_gridData.topPadding + _gridData.bottomPadding); - rect.origin.y += _gridData.topPadding; - + + if(self.layoutDirection == AQGridViewLayoutDirectionVertical){ + rect.size.width = self.bounds.size.width; + rect.size.height = self.contentSize.height - (_gridData.topPadding + _gridData.bottomPadding); + rect.origin.y += _gridData.topPadding; + }else{ + rect.size.height = self.bounds.size.height; + rect.size.width = self.contentSize.width - (_gridData.leftPadding + _gridData.rightPadding); + rect.origin.x += _gridData.leftPadding; + } // Make sure background is an integral number of rows tall. That way, it draws patterned colours correctly on all OSes. CGRect backgroundRect = rect; - - if ([self backgroundViewExtendsUp]) { - backgroundRect.origin.y = backgroundRect.origin.y - MAX_BOUNCE_DISTANCE; - backgroundRect.size.height += MAX_BOUNCE_DISTANCE; // don't just move it, grow it - } - - if ([self backgroundViewExtendsDown]) { - backgroundRect.size.height = backgroundRect.size.height + MAX_BOUNCE_DISTANCE; - } - + + CGFloat minimumHeight = rect.size.height, - actualHeight = 0; + minimumWidth = rect.size.width, + actualWidth = 0, + actualHeight = 0; + if(self.layoutDirection == AQGridViewLayoutDirectionVertical){ + if (([_gridData numberOfItems] == 0) || ([_gridData numberOfItemsPerRow] == 0)) { - if (([_gridData numberOfItems] == 0) || ([_gridData numberOfItemsPerRow] == 0)) { + actualHeight = [_gridData cellSize].height; - actualHeight = [_gridData cellSize].height; + } else { - } else { + actualHeight = [_gridData cellSize].height * (ceilf((CGFloat)[_gridData numberOfItems] / (CGFloat)[_gridData numberOfItemsPerRow]) + 1); - actualHeight = [_gridData cellSize].height * ([_gridData numberOfItems] / [_gridData numberOfItemsPerRow] + 1); + } + for (; actualHeight < minimumHeight; actualHeight += [_gridData cellSize].height) { + } + backgroundRect.size.height = actualHeight; + }else{ + if (([_gridData numberOfItems] == 0) || ([_gridData numberOfItemsPerColumn] == 0)) { + + actualWidth = [_gridData cellSize].width; + + } else { + + actualWidth = [_gridData cellSize].width * (ceilf((CGFloat)[_gridData numberOfItems] / (CGFloat)[_gridData numberOfItemsPerColumn]) + 1); - } - for (; actualHeight < minimumHeight; actualHeight += [_gridData cellSize].height) { - } - backgroundRect.size.height = actualHeight; + } + for (; actualWidth < minimumWidth; actualWidth += [_gridData cellSize].width) { + } + backgroundRect.size.width = actualWidth; + + } + if(self.layoutDirection == AQGridViewLayoutDirectionVertical){ + + if ([self backgroundViewExtendsUp]) { + backgroundRect.origin.y = backgroundRect.origin.y - MAX_BOUNCE_DISTANCE; + backgroundRect.size.height += MAX_BOUNCE_DISTANCE; // don't just move it, grow it + + } + + if ([self backgroundViewExtendsDown]) { + backgroundRect.size.height = backgroundRect.size.height + MAX_BOUNCE_DISTANCE; + } + } + else{ + if ([self backgroundViewExtendsLeft]) { + backgroundRect.origin.x = backgroundRect.origin.x - MAX_BOUNCE_DISTANCE; + backgroundRect.size.width += MAX_BOUNCE_DISTANCE; // don't just move it, grow it + } + + if ([self backgroundViewExtendsRight]) { + backgroundRect.size.width = backgroundRect.size.width + MAX_BOUNCE_DISTANCE; + } + } - self.backgroundView.frame = backgroundRect; + self.backgroundView.frame = backgroundRect; if ( _headerView != nil ) { @@ -719,6 +800,8 @@ - (void) layoutSubviews _footerView.frame = rect; [self bringSubviewToFront:_footerView]; } + + } - (CGRect) rectForItemAtIndex: (NSUInteger) index @@ -874,7 +957,12 @@ - (void) fixCellsFromAnimation //NSAssert([newVisibleCells count] == _visibleIndices.length, @"visible cell count after animation doesn't match visible indices"); - [newVisibleCells sortUsingSelector: @selector(compareOriginAgainstCell:)]; + if(self.layoutDirection == AQGridViewLayoutDirectionVertical){ + [newVisibleCells sortUsingSelector: @selector(compareOriginAgainstCellVertical:)]; + }else + { + [newVisibleCells sortUsingSelector: @selector(compareOriginAgainstCellHorizontal:)]; + } [_visibleCells removeObjectsInArray: newVisibleCells]; [_visibleCells makeObjectsPerformSelector: @selector(removeFromSuperview)]; [_visibleCells setArray: newVisibleCells]; @@ -1190,14 +1278,26 @@ - (void) setBackgroundView: (UIView *) newView CGRect backgroundRect = CGRectMake(0.0f, 0.0f, self.bounds.size.width, self.bounds.size.height); - if ([self backgroundViewExtendsUp]) { - backgroundRect.origin.y = backgroundRect.origin.y - MAX_BOUNCE_DISTANCE; - backgroundRect.size.height += MAX_BOUNCE_DISTANCE; // don't just move it, grow it - } + if(self.layoutDirection == AQGridViewLayoutDirectionVertical){ + if ([self backgroundViewExtendsUp]) { + backgroundRect.origin.y = backgroundRect.origin.y - MAX_BOUNCE_DISTANCE; + backgroundRect.size.height += MAX_BOUNCE_DISTANCE; // don't just move it, grow it + } - if ([self backgroundViewExtendsDown]) { - backgroundRect.size.height = backgroundRect.size.height + MAX_BOUNCE_DISTANCE; + if ([self backgroundViewExtendsDown]) { + backgroundRect.size.height = backgroundRect.size.height + MAX_BOUNCE_DISTANCE; + } + }else{ + + if ([self backgroundViewExtendsLeft]) { + backgroundRect.origin.x = backgroundRect.origin.x - MAX_BOUNCE_DISTANCE; + backgroundRect.size.width += MAX_BOUNCE_DISTANCE; // don't just move it, grow it } + + if ([self backgroundViewExtendsRight]) { + backgroundRect.size.width = backgroundRect.size.width + MAX_BOUNCE_DISTANCE; + } + } _backgroundView.frame = backgroundRect; diff --git a/Classes/AQGridViewCell.h b/Classes/AQGridViewCell.h index e23f978..8a032b2 100644 --- a/Classes/AQGridViewCell.h +++ b/Classes/AQGridViewCell.h @@ -122,6 +122,6 @@ typedef enum { - (void)setEditing:(BOOL)editing animated:(BOOL)animated; // Sorting -- (NSComparisonResult) compareOriginAgainstCell: (AQGridViewCell *) otherCell; - +- (NSComparisonResult) compareOriginAgainstCellVertical: (AQGridViewCell *) otherCell; +- (NSComparisonResult) compareOriginAgainstCellHorizontal: (AQGridViewCell *) otherCell; @end diff --git a/Classes/AQGridViewCell.m b/Classes/AQGridViewCell.m index 7cc9b68..b353a53 100644 --- a/Classes/AQGridViewCell.m +++ b/Classes/AQGridViewCell.m @@ -100,7 +100,8 @@ - (void) dealloc CFRelease( _selectionColorInfo ); } -- (NSComparisonResult) compareOriginAgainstCell: (AQGridViewCell *) otherCell + +- (NSComparisonResult) compareOriginAgainstCellVertical: (AQGridViewCell *) otherCell { CGPoint myOrigin = self.frame.origin; CGPoint theirOrigin = otherCell.frame.origin; @@ -118,6 +119,25 @@ - (NSComparisonResult) compareOriginAgainstCell: (AQGridViewCell *) otherCell return ( NSOrderedSame ); } +- (NSComparisonResult) compareOriginAgainstCellHorizontal: (AQGridViewCell *) otherCell +{ + CGPoint myOrigin = self.frame.origin; + CGPoint theirOrigin = otherCell.frame.origin; + + if ( myOrigin.x > theirOrigin.x ) + return ( NSOrderedDescending ); + else if ( myOrigin.x < theirOrigin.x ) + return ( NSOrderedAscending ); + + if ( myOrigin.y > theirOrigin.y ) + return ( NSOrderedDescending ); + else if ( myOrigin.y < theirOrigin.y ) + return ( NSOrderedAscending ); + + return ( NSOrderedSame ); +} + + - (UIView *) contentView { if ( _contentView == nil ) diff --git a/Classes/AQGridViewData.h b/Classes/AQGridViewData.h index 58b9dc6..cfeb4f0 100644 --- a/Classes/AQGridViewData.h +++ b/Classes/AQGridViewData.h @@ -80,6 +80,8 @@ - (CGRect) rectForEntireGrid; - (CGSize) sizeForEntireGrid; - (NSUInteger) numberOfItemsPerRow; +- (NSUInteger) numberOfItemsPerColumn; + - (CGRect) cellRectAtIndex: (NSUInteger) index; - (CGRect) cellRectForPoint: (CGPoint) point; diff --git a/Classes/AQGridViewData.m b/Classes/AQGridViewData.m index 876907b..f497b7a 100644 --- a/Classes/AQGridViewData.m +++ b/Classes/AQGridViewData.m @@ -39,6 +39,7 @@ @interface AQGridViewData (AQGridViewDataPrivate) - (void) fixDesiredCellSizeForWidth: (CGFloat) width; +- (void) fixDesiredCellSizeForHeight: (CGFloat) height; @end @implementation AQGridViewData @@ -82,6 +83,8 @@ - (void) gridViewDidChangeBoundsSize: (CGSize) boundsSize _boundsSize = boundsSize; if ( _layoutDirection == AQGridViewLayoutDirectionVertical ) [self fixDesiredCellSizeForWidth: boundsSize.width]; + else + [self fixDesiredCellSizeForHeight:boundsSize.height]; } - (NSUInteger) itemIndexForPoint: (CGPoint) point @@ -98,7 +101,13 @@ - (NSUInteger) itemIndexForPoint: (CGPoint) point NSUInteger x = (NSUInteger)floorf(point.x); NSUInteger col = x / (NSUInteger)_actualCellSize.width; - NSUInteger result = (row * [self numberOfItemsPerRow]) + col; + NSUInteger result; + if (_layoutDirection == AQGridViewLayoutDirectionVertical) { + result = (row * [self numberOfItemsPerRow]) + col; + }else{ + result = (col * [self numberOfItemsPerColumn]) + row; + } + if ( result >= self.numberOfItems ) result = NSNotFound; @@ -126,7 +135,7 @@ - (void) setDesiredCellSize: (CGSize) desiredCellSize if ( _layoutDirection == AQGridViewLayoutDirectionVertical ) [self fixDesiredCellSizeForWidth: _boundsSize.width]; else - _actualCellSize = _desiredCellSize; + [self fixDesiredCellSizeForHeight:_boundsSize.height]; } - (void) setLayoutDirection: (AQGridViewLayoutDirection) direction @@ -134,7 +143,7 @@ - (void) setLayoutDirection: (AQGridViewLayoutDirection) direction if ( direction == AQGridViewLayoutDirectionVertical ) [self fixDesiredCellSizeForWidth: _boundsSize.width]; else - _actualCellSize = _desiredCellSize; + [self fixDesiredCellSizeForHeight:_boundsSize.height]; _layoutDirection = direction; } @@ -154,18 +163,35 @@ - (CGRect) rectForEntireGrid - (CGSize) sizeForEntireGrid { - NSUInteger numPerRow = [self numberOfItemsPerRow]; - if ( numPerRow == 0 ) // avoid a divide-by-zero exception - return ( CGSizeZero ); - NSUInteger numRows = _numberOfItems / numPerRow; - if ( _numberOfItems % numPerRow != 0 ) - numRows++; - - CGFloat height = ( ((CGFloat)ceilf((CGFloat)numRows * _actualCellSize.height)) + _topPadding + _bottomPadding ); - if (height < _gridView.bounds.size.height) - height = _gridView.bounds.size.height; + if ( _layoutDirection == AQGridViewLayoutDirectionVertical ){ + + NSUInteger numPerRow = [self numberOfItemsPerRow]; + if ( numPerRow == 0 ) // avoid a divide-by-zero exception + return ( CGSizeZero ); + NSUInteger numRows = _numberOfItems / numPerRow; + if ( _numberOfItems % numPerRow != 0 ) + numRows++; - return ( CGSizeMake(((CGFloat)ceilf(_actualCellSize.width * numPerRow)) + _leftPadding + _rightPadding, height) ); + CGFloat height = ( ((CGFloat)ceilf((CGFloat)numRows * _actualCellSize.height)) + _topPadding + _bottomPadding ); + if (height < _gridView.bounds.size.height) + height = _gridView.bounds.size.height; + + return ( CGSizeMake(((CGFloat)ceilf(_actualCellSize.width * numPerRow)) + _leftPadding + _rightPadding, height) ); + }else{ + NSUInteger numPerCol = [self numberOfItemsPerColumn]; + if ( numPerCol == 0 ) // avoid a divide-by-zero exception + return ( CGSizeZero ); + NSUInteger numCols = _numberOfItems / numPerCol; + if ( _numberOfItems % numPerCol != 0 ) + numCols++; + + CGFloat width = ( ((CGFloat)ceilf((CGFloat)numCols * _actualCellSize.width)) + _leftPadding + _rightPadding ); + if (width < _gridView.bounds.size.width) + width = _gridView.bounds.size.width; + + return ( CGSizeMake(width,((CGFloat)ceilf(_actualCellSize.height * numPerCol)) + _topPadding + _bottomPadding) ); + } + } - (NSUInteger) numberOfItemsPerRow @@ -185,49 +211,99 @@ - (NSUInteger) numberOfItemsPerRow return ( cols ); } +- (NSUInteger) numberOfItemsPerColumn +{ + NSUInteger cols = (NSUInteger)floorf(_boundsSize.height / _actualCellSize.height); + return ( cols ); +} + + - (CGRect) cellRectAtIndex: (NSUInteger) index { - NSUInteger numPerRow = [self numberOfItemsPerRow]; - if ( numPerRow == 0 ) // avoid a divide-by-zero exception - return ( CGRectZero ); - NSUInteger skipRows = index / numPerRow; - NSUInteger skipCols = index % numPerRow; + CGRect result = CGRectZero; + if ( _layoutDirection == AQGridViewLayoutDirectionVertical){ + + NSUInteger numPerRow = [self numberOfItemsPerRow]; + if ( numPerRow == 0 ) // avoid a divide-by-zero exception + return ( CGRectZero ); + NSUInteger skipRows = index / numPerRow; + NSUInteger skipCols = index % numPerRow; - CGRect result = CGRectZero; - result.origin.x = _actualCellSize.width * (CGFloat)skipCols + _leftPadding; - result.origin.y = (_actualCellSize.height * (CGFloat)skipRows) + _topPadding; - result.size = _actualCellSize; + result.origin.x = _actualCellSize.width * (CGFloat)skipCols + _leftPadding; + result.origin.y = (_actualCellSize.height * (CGFloat)skipRows) + _topPadding; + result.size = _actualCellSize; - return ( result ); + }else{ + + NSUInteger numPerCol = [self numberOfItemsPerColumn]; + if (numPerCol == 0) + return (CGRectZero); + NSUInteger skipCols = index / numPerCol; + NSUInteger skipRows = index % numPerCol; + + result.origin.x = _actualCellSize.width * (CGFloat)skipCols + _leftPadding; + result.origin.y = (_actualCellSize.height * (CGFloat)skipRows) + _topPadding; + result.size = _actualCellSize; + } + return ( result ); + } - (NSIndexSet *) indicesOfCellsInRect: (CGRect) aRect { NSMutableIndexSet * result = [NSMutableIndexSet indexSet]; - NSUInteger numPerRow = [self numberOfItemsPerRow]; + if ( _layoutDirection == AQGridViewLayoutDirectionVertical){ + + NSUInteger numPerRow = [self numberOfItemsPerRow]; - for ( NSUInteger i = 0; i < _numberOfItems; i++ ) - { - CGRect cellRect = [self cellRectAtIndex: i]; - - if ( CGRectGetMaxY(cellRect) < CGRectGetMinY(aRect) ) - { - // jump forward to the next row - i += (numPerRow - 1); - continue; - } + for ( NSUInteger i = 0; i < _numberOfItems; i++ ) + { + CGRect cellRect = [self cellRectAtIndex: i]; + + if ( CGRectGetMaxY(cellRect) < CGRectGetMinY(aRect) ) + { + // jump forward to the next row + i += (numPerRow - 1); + continue; + } - if ( CGRectIntersectsRect(cellRect, aRect) ) - { - [result addIndex: i]; - if ( (CGRectGetMaxY(cellRect) > CGRectGetMaxY(aRect)) && - (CGRectGetMaxX(cellRect) > CGRectGetMaxX(aRect)) ) - { - // passed the bottom-right edge of the given rect - break; - } - } - } + if ( CGRectIntersectsRect(cellRect, aRect) ) + { + [result addIndex: i]; + if ( (CGRectGetMaxY(cellRect) > CGRectGetMaxY(aRect)) && + (CGRectGetMaxX(cellRect) > CGRectGetMaxX(aRect)) ) + { + // passed the bottom-right edge of the given rect + break; + } + } + } + }else{ + NSUInteger numPerCol = [self numberOfItemsPerColumn]; + for ( NSUInteger i = 0; i < _numberOfItems; i++ ) + { + CGRect cellRect = [self cellRectAtIndex: i]; + + if ( CGRectGetMaxX(cellRect) < CGRectGetMinX(aRect) ) + { + // jump forward to the next column + i += (numPerCol - 1); + continue; + } + + if ( CGRectIntersectsRect(cellRect, aRect) ) + { + [result addIndex: i]; + if ( (CGRectGetMaxY(cellRect) > CGRectGetMaxY(aRect)) && + (CGRectGetMaxX(cellRect) > CGRectGetMaxX(aRect)) ) + { + // passed the bottom-right edge of the given rect + break; + } + } + } + } + return ( result ); } @@ -247,4 +323,17 @@ - (void) fixDesiredCellSizeForWidth: (CGFloat) width _actualCellSize.height = _desiredCellSize.height; } +- (void) fixDesiredCellSizeForHeight: (CGFloat) height +{ + + // Much thanks to Brandon Sneed (@bsneed) for the following new algorithm, reduced to two floating-point divisions -- that's O(1) folks! + CGFloat h = floorf(height - _topPadding - _bottomPadding); + CGFloat dh = floorf(_desiredCellSize.height); + CGFloat multiplier = floorf( h / dh ); + + _actualCellSize.height = floorf( h / multiplier ); + _actualCellSize.width = _desiredCellSize.width; + +} + @end