Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge branch 'master' of github.com:AlanQuatermain/AQGridView
* 'master' of github.com:AlanQuatermain/AQGridView:
  add indexForCell
  Updated README.
  Now raises an exception in -endUpdates if the expected new item count doesn't match the data source's value.
  Fixed layout issues for grid bounds changes while looking at bottom of grid view.
  Fixed a bug where -[AQGridViewData copyWithZone:] wasn't copying left or right padding, causing layout issues after animated insertion/deletion.
  Handle the case where the dragged-to cell index isn't in the grid by snapping the drag target back to the last place in the grid.
  Return NSNotFound when asked for the index of a grid cell at a point outside the bounds of the grid.
  Fixed a bug in the license.
  FIX: gridView isn't scrollable when the number of items fits into its view area
  add selectionGlowShadowRadius property

Conflicts:
	Classes/AQGridView.m
  • Loading branch information
AlanQuatermain committed Jun 9, 2010
2 parents 3f55b94 + 2a86b77 commit 2dbb525
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 43 deletions.
1 change: 1 addition & 0 deletions Classes/AQGridView.h
Expand Up @@ -172,6 +172,7 @@ extern NSString * const AQGridViewSelectionDidChangeNotification;
- (CGRect) rectForItemAtIndex: (NSUInteger) index;
- (AQGridViewCell *) cellForItemAtIndex: (NSUInteger) index;
- (NSUInteger) indexForItemAtPoint: (CGPoint) point;
- (NSUInteger) indexForCell: (AQGridViewCell *) cell;
- (AQGridViewCell *) cellForItemAtPoint: (CGPoint) point;

- (NSArray *) visibleCells;
Expand Down
150 changes: 126 additions & 24 deletions Classes/AQGridView.m
Expand Up @@ -407,11 +407,26 @@ - (void) updateContentRectWithOldMaxLocation: (CGPoint) oldMaxLocation gridSize:

- (void) handleGridViewBoundsChanged: (CGRect) oldBounds toNewBounds: (CGRect) bounds
{
CGSize oldGridSize = [_gridData sizeForEntireGrid];
BOOL wasAtBottom = CGRectGetMaxY(oldBounds) == oldGridSize.height;

[_gridData gridViewDidChangeBoundsSize: bounds.size];
_flags.numColumns = [_gridData numberOfItemsPerRow];
CGSize newGridSize = [_gridData sizeForEntireGrid];

CGPoint oldMaxLocation = CGPointMake(CGRectGetMaxX(oldBounds), CGRectGetMaxY(oldBounds));
[self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: [_gridData sizeForEntireGrid]];
[self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: newGridSize];

if ( (wasAtBottom) && (newGridSize.height > oldGridSize.height) )
{
CGRect contentRect = self.bounds;
if ( CGRectGetMaxY(contentRect) < newGridSize.height )
{
contentRect.origin.y += (newGridSize.height - oldGridSize.height);
self.contentOffset = contentRect.origin;
}
}

[self updateVisibleGridCellsNow];
_flags.allCellsNeedLayout = 1;
}
Expand Down Expand Up @@ -601,6 +616,15 @@ - (NSUInteger) indexForItemAtPoint: (CGPoint) point
return ( [_gridData itemIndexForPoint: point] );
}

- (NSUInteger) indexForCell: (AQGridViewCell *) cell
{
NSUInteger index = [_visibleCells indexOfObject:cell];
if (index == NSNotFound)
return NSNotFound;

return _visibleIndices.location + index;
}

- (AQGridViewCell *) cellForItemAtPoint: (CGPoint) point
{
return ( [self cellForItemAtIndex: [_gridData itemIndexForPoint: point]] );
Expand Down Expand Up @@ -697,6 +721,19 @@ - (void) fixCellsFromAnimation
self.animatingCells = nil;
_revealingIndices.length = _revealingIndices.location = 0;

NSMutableSet * removals = [[NSMutableSet alloc] init];
for ( UIView * view in self.subviews )
{
if ( [view isKindOfClass: [AQGridViewCell class]] == NO )
continue;

if ( [_visibleCells containsObject: view] == NO )
[removals addObject: view];
}

[removals makeObjectsPerformSelector: @selector(removeFromSuperview)];
[removals release];

// update the content size/offset based on the new grid data
CGPoint oldMaxLocation = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
[self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: [_gridData sizeForEntireGrid]];
Expand All @@ -719,11 +756,29 @@ - (void) endUpdateAnimations
if ( _updateInfo.numberOfUpdates == 0 )
{
//_reloadingSuspendedCount--;
_flags.isAnimatingUpdates = 0;
_flags.updating = 0;
[_updateInfo release];
_updateInfo = nil;
return;
}

NSUInteger expectedItemCount = [_updateInfo numberOfItemsAfterUpdates];
NSUInteger actualItemCount = [_dataSource numberOfItemsInGridView: self];
if ( expectedItemCount != actualItemCount )
{
NSUInteger numAdded = [[_updateInfo sortedInsertItems] count];
NSUInteger numDeleted = [[_updateInfo sortedDeleteItems] count];

//_reloadingSuspendedCount--;
_flags.isAnimatingUpdates = 0;
_flags.updating = 0;
[_updateInfo release];
_updateInfo = nil;

[NSException raise: NSInternalInconsistencyException format: @"Invalid number of items in AQGridView: Started with %u, added %u, deleted %u. Expected %u items after changes, but got %u", (unsigned)_gridData.numberOfItems, (unsigned)numAdded, (unsigned)numDeleted, (unsigned)expectedItemCount, (unsigned)actualItemCount];
}

[_updateInfo cleanupUpdateItems];

[UIView beginAnimations: @"CellUpdates" context: nil];
Expand Down Expand Up @@ -819,26 +874,6 @@ - (NSUInteger) indexOfSelectedItem
return ( _selectedIndex );
}

- (void) selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
scrollPosition: (AQGridViewScrollPosition) scrollPosition
{
if ( _selectedIndex != NSNotFound )
[self deselectItemAtIndex: _selectedIndex animated: NO];

_selectedIndex = index;
[self scrollToItemAtIndex: index atScrollPosition: AQGridViewScrollPositionNone animated: animated];
}

- (void) deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
{
AQGridViewCell * cell = [self cellForItemAtIndex: index];
if ( cell != nil )
[cell setSelected: NO animated: animated];

if ( _selectedIndex == index )
_selectedIndex = NSNotFound;
}

- (void) highlightItemAtIndex: (NSUInteger) index animated: (BOOL) animated scrollPosition: (AQGridViewScrollPosition) position
{
if ( [_highlightedIndices containsIndex: index] )
Expand Down Expand Up @@ -876,6 +911,11 @@ - (void) unhighlightItemAtIndex: (NSUInteger) index animated: (BOOL) animated
return;

[_highlightedIndices removeIndex: index];

// don't remove highlighting if the cell is actually the selected cell
if ( index == _selectedIndex )
return;

AQGridViewCell * cell = [self cellForItemAtIndex: index];
if ( cell != nil )
[cell setHighlighted: NO animated: animated];
Expand Down Expand Up @@ -909,7 +949,7 @@ - (void) _selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
return; // already selected this item

if ( _selectedIndex != NSNotFound )
[self _deselectItemAtIndex: _selectedIndex animated: animated notifyDelegate: NO];
[self _deselectItemAtIndex: _selectedIndex animated: animated notifyDelegate: notifyDelegate];

if ( _flags.allowsSelection == 0 )
return;
Expand All @@ -931,6 +971,20 @@ - (void) _selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated

if ( notifyDelegate && _flags.delegateDidSelectItem )
[self.delegate gridView: self didSelectItemAtIndex: index];

// ensure that the selected item is no longer marked as just 'highlighted' (that's an intermediary state)
[_highlightedIndices removeIndex: index];
}

- (void) selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
scrollPosition: (AQGridViewScrollPosition) scrollPosition
{
[self _selectItemAtIndex: index animated: animated scrollPosition: scrollPosition notifyDelegate: NO];
}

- (void) deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
{
[self _deselectItemAtIndex: index animated: animated notifyDelegate: NO];
}

#pragma mark -
Expand Down Expand Up @@ -1029,6 +1083,43 @@ - (void) _userSelectItemAtIndex: (NSNumber *) indexNum
_pendingSelectionIndex = NSNotFound;
}

- (BOOL) _gestureRecognizerIsHandlingTouches: (NSSet *) touches
{
// see if the touch is (possibly) being tracked by a gesture recognizer
for ( id recognizer in self.gestureRecognizers )
{
switch ( [recognizer state] )
{
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateFailed:
continue;

default:
break;
}

if ( [recognizer numberOfTouches] == [touches count] )
{
// simple version:
// pick a touch from our event's set, and see if it's in the recognizer's set
UITouch * touch = [touches anyObject];
CGPoint touchLocation = [touch locationInView: self];

for ( NSUInteger i = 0; i < [recognizer numberOfTouches]; i++ )
{
CGPoint test = [recognizer locationOfTouch: i inView: self];
if ( CGPointEqualToPoint(test, touchLocation) )
{
return ( YES );
}
}
}
}

return ( NO );
}

- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
_flags.ignoreTouchSelect = ([self isDragging] ? 1 : 0);
Expand Down Expand Up @@ -1080,12 +1171,20 @@ - (void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event
{
if ( _flags.ignoreTouchSelect == 0 )
{
Class cls = NSClassFromString(@"UILongPressGestureRecognizer");
if ( (cls != Nil) && ([cls instancesRespondToSelector: @selector(setNumberOfTouchesRequired:)]) )
{
if ( [self _gestureRecognizerIsHandlingTouches: touches] )
goto passToSuper; // I feel all icky now
}

//[self _cancelContentTouchUsingEvent: event forced: NO];
[self highlightItemAtIndex: NSNotFound animated: NO scrollPosition: AQGridViewScrollPositionNone];
_flags.ignoreTouchSelect = 1;
_touchedContentView = nil;
}

passToSuper:
[super touchesMoved: touches withEvent: event];
}

Expand Down Expand Up @@ -1180,6 +1279,9 @@ - (void) updateVisibleGridCellsNow
if ( _reloadingSuspendedCount > 0 )
return;

if ( _flags.isAnimatingUpdates || _flags.updating )
return;

_reloadingSuspendedCount++;
NSIndexSet * newVisibleIndices = [_gridData indicesOfCellsInRect: [self gridViewVisibleBounds]];

Expand Down Expand Up @@ -1456,8 +1558,8 @@ - (void) viewWillRotateToInterfaceOrientation: (UIInterfaceOrientation) orientat
// to avoid cell pop-in or pop-out:
// if we're switching to landscape, don't update cells until after the transition.
// if we're switching to portrait, update cells first.
if ( UIInterfaceOrientationIsLandscape(orientation) )
_reloadingSuspendedCount++;
//if ( UIInterfaceOrientationIsLandscape(orientation) )
// _reloadingSuspendedCount++;
}

- (void) viewDidRotate
Expand Down
2 changes: 2 additions & 0 deletions Classes/AQGridViewCell.h
Expand Up @@ -65,6 +65,7 @@ typedef enum {
UIColor * _backgroundColor;
UIColor * _separatorColor;
UIColor * _selectionGlowColor;
CGFloat _selectionGlowShadowRadius;
UIView * _bottomSeparatorView;
UIView * _rightSeparatorView;
NSTimer * _fadeTimer;
Expand Down Expand Up @@ -103,6 +104,7 @@ typedef enum {
@property (nonatomic, getter=isSelected) BOOL selected; // default is NO
@property (nonatomic, getter=isHighlighted) BOOL highlighted; // default is NO
@property (nonatomic, retain) UIColor * selectionGlowColor; // default is dark grey, ignored if selectionStyle != AQGridViewCellSelectionStyleGlow
@property (nonatomic) CGFloat selectionGlowShadowRadius; // default is 12.0, ignored if selectionStyle != AQGridViewCellSelectionStyleGlow

// this can be overridden by subclasses to return a subview's layer to which to add the glow
// the default implementation returns the contentView's layer
Expand Down
30 changes: 19 additions & 11 deletions Classes/AQGridViewCell.m
Expand Up @@ -49,6 +49,7 @@ @implementation AQGridViewCell

@synthesize contentView=_contentView, backgroundView=_backgroundView, selectedBackgroundView=_selectedBackgroundView;
@synthesize reuseIdentifier=_reuseIdentifier, selectionGlowColor=_selectionGlowColor;
@synthesize selectionGlowShadowRadius=_selectionGlowShadowRadius;

- (id) initWithFrame: (CGRect) frame reuseIdentifier: (NSString *) reuseIdentifier
{
Expand All @@ -68,6 +69,8 @@ - (id) initWithFrame: (CGRect) frame reuseIdentifier: (NSString *) reuseIdentifi
_selectionColorInfo = CFDictionaryCreateMutable( kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
self.backgroundColor = [UIColor whiteColor];

_selectionGlowShadowRadius = 12.0f;

return ( self );
}

Expand Down Expand Up @@ -256,10 +259,16 @@ - (void) highlightSubviewsOfView: (UIView *) aView
CFDictionarySetValue( _selectionColorInfo, view, info );
}

id value = [view valueForKey: @"highlighted"];
if ( value == nil )
value = [NSNumber numberWithBool: NO];
[info setObject: value forKey: @"highlighted"];
// don't overwrite any prior cache of a view's original highlighted state.
// this is because 'highlighted' will be set, then 'selected', which can perform 'highlight' again before the animation completes
if ( [info objectForKey: @"highlighted"] == nil )
{
id value = [view valueForKey: @"highlighted"];
if ( value == nil )
value = [NSNumber numberWithBool: NO];
[info setObject: value forKey: @"highlighted"];
}

[view setValue: [NSNumber numberWithBool: YES]
forKey: @"highlighted"];
}
Expand Down Expand Up @@ -340,12 +349,6 @@ - (void) _beginBackgroundHighlight: (BOOL) highlightOn animated: (BOOL) animated
}
else
{
[UIView setAnimationsEnabled: NO];
// find all non-opaque subviews and make opaque again, with original background colors
[self makeSubviewsOfViewOpaqueAgain: self];
[UIView setAnimationsEnabled: YES];

// now we're animating once more
_selectedBackgroundView.alpha = 0.0;
}

Expand Down Expand Up @@ -392,6 +395,11 @@ - (void) highlightAnimationStopped: (NSString * __unused) animationID context: (
}
else
{
[UIView setAnimationsEnabled: NO];
// find all non-opaque subviews and make opaque again, with original background colors
[self makeSubviewsOfViewOpaqueAgain: self];
[UIView setAnimationsEnabled: YES];

_cellFlags.highlighted = 0;
[_selectedBackgroundView removeFromSuperview];
CFDictionaryRemoveAllValues( _selectionColorInfo );
Expand Down Expand Up @@ -457,7 +465,7 @@ - (void) setHighlighted: (BOOL) value animated: (BOOL) animated
else
theLayer.shadowColor = [[UIColor darkGrayColor] CGColor];

theLayer.shadowRadius = 12.0;
theLayer.shadowRadius = self.selectionGlowShadowRadius;

// add or remove the 'shadow' as appropriate
if ( value )
Expand Down
15 changes: 12 additions & 3 deletions Classes/AQGridViewData.m
Expand Up @@ -65,6 +65,8 @@ - (id) copyWithZone: (NSZone *) zone
theCopy->_layoutDirection = _layoutDirection;
theCopy->_topPadding = _topPadding;
theCopy->_bottomPadding = _bottomPadding;
theCopy->_leftPadding = _leftPadding;
theCopy->_rightPadding = _rightPadding;
theCopy->_numberOfItems = _numberOfItems;
theCopy->_reorderedIndex = _reorderedIndex;
return ( theCopy );
Expand Down Expand Up @@ -96,7 +98,11 @@ - (NSUInteger) itemIndexForPoint: (CGPoint) point
NSUInteger x = (NSUInteger)floorf(point.x);
NSUInteger col = x / (NSUInteger)_actualCellSize.width;

return ( (row * [self numberOfItemsPerRow]) + col );
NSUInteger result = (row * [self numberOfItemsPerRow]) + col;
if ( result >= self.numberOfItems )
result = NSNotFound;

return ( result );
}

- (CGRect) cellRectForPoint: (CGPoint) point
Expand Down Expand Up @@ -145,8 +151,11 @@ - (CGSize) sizeForEntireGrid
if ( _numberOfItems % numPerRow != 0 )
numRows++;

return ( CGSizeMake(((CGFloat)ceilf(_actualCellSize.width * numPerRow)) + _leftPadding + _rightPadding,
((CGFloat)ceilf((CGFloat)numRows * _actualCellSize.height)) + _topPadding + _bottomPadding) );
CGFloat height = ( ((CGFloat)ceilf((CGFloat)numRows * _actualCellSize.height)) + _topPadding + _bottomPadding );
if (height < _gridView.bounds.size.height)
height = _gridView.bounds.size.height + 1;

return ( CGSizeMake(((CGFloat)ceilf(_actualCellSize.width * numPerRow)) + _leftPadding + _rightPadding, height) );
}

- (NSUInteger) numberOfItemsPerRow
Expand Down
1 change: 1 addition & 0 deletions Classes/AQGridViewUpdateInfo.h
Expand Up @@ -98,6 +98,7 @@
- (NSArray *) sortedReloadItems;

- (AQGridViewData *) newGridViewData;
- (NSUInteger) numberOfItemsAfterUpdates;

- (NSUInteger) newIndexForOldIndex: (NSUInteger) oldIndex;

Expand Down

0 comments on commit 2dbb525

Please sign in to comment.