Skip to content

Commit 2dbb525

Browse files
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
2 parents 3f55b94 + 2a86b77 commit 2dbb525

10 files changed

Lines changed: 184 additions & 43 deletions

File tree

Classes/AQGridView.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ extern NSString * const AQGridViewSelectionDidChangeNotification;
172172
- (CGRect) rectForItemAtIndex: (NSUInteger) index;
173173
- (AQGridViewCell *) cellForItemAtIndex: (NSUInteger) index;
174174
- (NSUInteger) indexForItemAtPoint: (CGPoint) point;
175+
- (NSUInteger) indexForCell: (AQGridViewCell *) cell;
175176
- (AQGridViewCell *) cellForItemAtPoint: (CGPoint) point;
176177

177178
- (NSArray *) visibleCells;

Classes/AQGridView.m

Lines changed: 126 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -407,11 +407,26 @@ - (void) updateContentRectWithOldMaxLocation: (CGPoint) oldMaxLocation gridSize:
407407

408408
- (void) handleGridViewBoundsChanged: (CGRect) oldBounds toNewBounds: (CGRect) bounds
409409
{
410+
CGSize oldGridSize = [_gridData sizeForEntireGrid];
411+
BOOL wasAtBottom = CGRectGetMaxY(oldBounds) == oldGridSize.height;
412+
410413
[_gridData gridViewDidChangeBoundsSize: bounds.size];
411414
_flags.numColumns = [_gridData numberOfItemsPerRow];
415+
CGSize newGridSize = [_gridData sizeForEntireGrid];
412416

413417
CGPoint oldMaxLocation = CGPointMake(CGRectGetMaxX(oldBounds), CGRectGetMaxY(oldBounds));
414-
[self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: [_gridData sizeForEntireGrid]];
418+
[self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: newGridSize];
419+
420+
if ( (wasAtBottom) && (newGridSize.height > oldGridSize.height) )
421+
{
422+
CGRect contentRect = self.bounds;
423+
if ( CGRectGetMaxY(contentRect) < newGridSize.height )
424+
{
425+
contentRect.origin.y += (newGridSize.height - oldGridSize.height);
426+
self.contentOffset = contentRect.origin;
427+
}
428+
}
429+
415430
[self updateVisibleGridCellsNow];
416431
_flags.allCellsNeedLayout = 1;
417432
}
@@ -601,6 +616,15 @@ - (NSUInteger) indexForItemAtPoint: (CGPoint) point
601616
return ( [_gridData itemIndexForPoint: point] );
602617
}
603618

619+
- (NSUInteger) indexForCell: (AQGridViewCell *) cell
620+
{
621+
NSUInteger index = [_visibleCells indexOfObject:cell];
622+
if (index == NSNotFound)
623+
return NSNotFound;
624+
625+
return _visibleIndices.location + index;
626+
}
627+
604628
- (AQGridViewCell *) cellForItemAtPoint: (CGPoint) point
605629
{
606630
return ( [self cellForItemAtIndex: [_gridData itemIndexForPoint: point]] );
@@ -697,6 +721,19 @@ - (void) fixCellsFromAnimation
697721
self.animatingCells = nil;
698722
_revealingIndices.length = _revealingIndices.location = 0;
699723

724+
NSMutableSet * removals = [[NSMutableSet alloc] init];
725+
for ( UIView * view in self.subviews )
726+
{
727+
if ( [view isKindOfClass: [AQGridViewCell class]] == NO )
728+
continue;
729+
730+
if ( [_visibleCells containsObject: view] == NO )
731+
[removals addObject: view];
732+
}
733+
734+
[removals makeObjectsPerformSelector: @selector(removeFromSuperview)];
735+
[removals release];
736+
700737
// update the content size/offset based on the new grid data
701738
CGPoint oldMaxLocation = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
702739
[self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: [_gridData sizeForEntireGrid]];
@@ -719,11 +756,29 @@ - (void) endUpdateAnimations
719756
if ( _updateInfo.numberOfUpdates == 0 )
720757
{
721758
//_reloadingSuspendedCount--;
759+
_flags.isAnimatingUpdates = 0;
760+
_flags.updating = 0;
722761
[_updateInfo release];
723762
_updateInfo = nil;
724763
return;
725764
}
726765

766+
NSUInteger expectedItemCount = [_updateInfo numberOfItemsAfterUpdates];
767+
NSUInteger actualItemCount = [_dataSource numberOfItemsInGridView: self];
768+
if ( expectedItemCount != actualItemCount )
769+
{
770+
NSUInteger numAdded = [[_updateInfo sortedInsertItems] count];
771+
NSUInteger numDeleted = [[_updateInfo sortedDeleteItems] count];
772+
773+
//_reloadingSuspendedCount--;
774+
_flags.isAnimatingUpdates = 0;
775+
_flags.updating = 0;
776+
[_updateInfo release];
777+
_updateInfo = nil;
778+
779+
[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];
780+
}
781+
727782
[_updateInfo cleanupUpdateItems];
728783

729784
[UIView beginAnimations: @"CellUpdates" context: nil];
@@ -819,26 +874,6 @@ - (NSUInteger) indexOfSelectedItem
819874
return ( _selectedIndex );
820875
}
821876

822-
- (void) selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
823-
scrollPosition: (AQGridViewScrollPosition) scrollPosition
824-
{
825-
if ( _selectedIndex != NSNotFound )
826-
[self deselectItemAtIndex: _selectedIndex animated: NO];
827-
828-
_selectedIndex = index;
829-
[self scrollToItemAtIndex: index atScrollPosition: AQGridViewScrollPositionNone animated: animated];
830-
}
831-
832-
- (void) deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
833-
{
834-
AQGridViewCell * cell = [self cellForItemAtIndex: index];
835-
if ( cell != nil )
836-
[cell setSelected: NO animated: animated];
837-
838-
if ( _selectedIndex == index )
839-
_selectedIndex = NSNotFound;
840-
}
841-
842877
- (void) highlightItemAtIndex: (NSUInteger) index animated: (BOOL) animated scrollPosition: (AQGridViewScrollPosition) position
843878
{
844879
if ( [_highlightedIndices containsIndex: index] )
@@ -876,6 +911,11 @@ - (void) unhighlightItemAtIndex: (NSUInteger) index animated: (BOOL) animated
876911
return;
877912

878913
[_highlightedIndices removeIndex: index];
914+
915+
// don't remove highlighting if the cell is actually the selected cell
916+
if ( index == _selectedIndex )
917+
return;
918+
879919
AQGridViewCell * cell = [self cellForItemAtIndex: index];
880920
if ( cell != nil )
881921
[cell setHighlighted: NO animated: animated];
@@ -909,7 +949,7 @@ - (void) _selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
909949
return; // already selected this item
910950

911951
if ( _selectedIndex != NSNotFound )
912-
[self _deselectItemAtIndex: _selectedIndex animated: animated notifyDelegate: NO];
952+
[self _deselectItemAtIndex: _selectedIndex animated: animated notifyDelegate: notifyDelegate];
913953

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

932972
if ( notifyDelegate && _flags.delegateDidSelectItem )
933973
[self.delegate gridView: self didSelectItemAtIndex: index];
974+
975+
// ensure that the selected item is no longer marked as just 'highlighted' (that's an intermediary state)
976+
[_highlightedIndices removeIndex: index];
977+
}
978+
979+
- (void) selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
980+
scrollPosition: (AQGridViewScrollPosition) scrollPosition
981+
{
982+
[self _selectItemAtIndex: index animated: animated scrollPosition: scrollPosition notifyDelegate: NO];
983+
}
984+
985+
- (void) deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
986+
{
987+
[self _deselectItemAtIndex: index animated: animated notifyDelegate: NO];
934988
}
935989

936990
#pragma mark -
@@ -1029,6 +1083,43 @@ - (void) _userSelectItemAtIndex: (NSNumber *) indexNum
10291083
_pendingSelectionIndex = NSNotFound;
10301084
}
10311085

1086+
- (BOOL) _gestureRecognizerIsHandlingTouches: (NSSet *) touches
1087+
{
1088+
// see if the touch is (possibly) being tracked by a gesture recognizer
1089+
for ( id recognizer in self.gestureRecognizers )
1090+
{
1091+
switch ( [recognizer state] )
1092+
{
1093+
case UIGestureRecognizerStateEnded:
1094+
case UIGestureRecognizerStateCancelled:
1095+
case UIGestureRecognizerStateFailed:
1096+
continue;
1097+
1098+
default:
1099+
break;
1100+
}
1101+
1102+
if ( [recognizer numberOfTouches] == [touches count] )
1103+
{
1104+
// simple version:
1105+
// pick a touch from our event's set, and see if it's in the recognizer's set
1106+
UITouch * touch = [touches anyObject];
1107+
CGPoint touchLocation = [touch locationInView: self];
1108+
1109+
for ( NSUInteger i = 0; i < [recognizer numberOfTouches]; i++ )
1110+
{
1111+
CGPoint test = [recognizer locationOfTouch: i inView: self];
1112+
if ( CGPointEqualToPoint(test, touchLocation) )
1113+
{
1114+
return ( YES );
1115+
}
1116+
}
1117+
}
1118+
}
1119+
1120+
return ( NO );
1121+
}
1122+
10321123
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
10331124
{
10341125
_flags.ignoreTouchSelect = ([self isDragging] ? 1 : 0);
@@ -1080,12 +1171,20 @@ - (void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event
10801171
{
10811172
if ( _flags.ignoreTouchSelect == 0 )
10821173
{
1174+
Class cls = NSClassFromString(@"UILongPressGestureRecognizer");
1175+
if ( (cls != Nil) && ([cls instancesRespondToSelector: @selector(setNumberOfTouchesRequired:)]) )
1176+
{
1177+
if ( [self _gestureRecognizerIsHandlingTouches: touches] )
1178+
goto passToSuper; // I feel all icky now
1179+
}
1180+
10831181
//[self _cancelContentTouchUsingEvent: event forced: NO];
10841182
[self highlightItemAtIndex: NSNotFound animated: NO scrollPosition: AQGridViewScrollPositionNone];
10851183
_flags.ignoreTouchSelect = 1;
10861184
_touchedContentView = nil;
10871185
}
10881186

1187+
passToSuper:
10891188
[super touchesMoved: touches withEvent: event];
10901189
}
10911190

@@ -1180,6 +1279,9 @@ - (void) updateVisibleGridCellsNow
11801279
if ( _reloadingSuspendedCount > 0 )
11811280
return;
11821281

1282+
if ( _flags.isAnimatingUpdates || _flags.updating )
1283+
return;
1284+
11831285
_reloadingSuspendedCount++;
11841286
NSIndexSet * newVisibleIndices = [_gridData indicesOfCellsInRect: [self gridViewVisibleBounds]];
11851287

@@ -1456,8 +1558,8 @@ - (void) viewWillRotateToInterfaceOrientation: (UIInterfaceOrientation) orientat
14561558
// to avoid cell pop-in or pop-out:
14571559
// if we're switching to landscape, don't update cells until after the transition.
14581560
// if we're switching to portrait, update cells first.
1459-
if ( UIInterfaceOrientationIsLandscape(orientation) )
1460-
_reloadingSuspendedCount++;
1561+
//if ( UIInterfaceOrientationIsLandscape(orientation) )
1562+
// _reloadingSuspendedCount++;
14611563
}
14621564

14631565
- (void) viewDidRotate

Classes/AQGridViewCell.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ typedef enum {
6565
UIColor * _backgroundColor;
6666
UIColor * _separatorColor;
6767
UIColor * _selectionGlowColor;
68+
CGFloat _selectionGlowShadowRadius;
6869
UIView * _bottomSeparatorView;
6970
UIView * _rightSeparatorView;
7071
NSTimer * _fadeTimer;
@@ -103,6 +104,7 @@ typedef enum {
103104
@property (nonatomic, getter=isSelected) BOOL selected; // default is NO
104105
@property (nonatomic, getter=isHighlighted) BOOL highlighted; // default is NO
105106
@property (nonatomic, retain) UIColor * selectionGlowColor; // default is dark grey, ignored if selectionStyle != AQGridViewCellSelectionStyleGlow
107+
@property (nonatomic) CGFloat selectionGlowShadowRadius; // default is 12.0, ignored if selectionStyle != AQGridViewCellSelectionStyleGlow
106108

107109
// this can be overridden by subclasses to return a subview's layer to which to add the glow
108110
// the default implementation returns the contentView's layer

Classes/AQGridViewCell.m

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ @implementation AQGridViewCell
4949

5050
@synthesize contentView=_contentView, backgroundView=_backgroundView, selectedBackgroundView=_selectedBackgroundView;
5151
@synthesize reuseIdentifier=_reuseIdentifier, selectionGlowColor=_selectionGlowColor;
52+
@synthesize selectionGlowShadowRadius=_selectionGlowShadowRadius;
5253

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

72+
_selectionGlowShadowRadius = 12.0f;
73+
7174
return ( self );
7275
}
7376

@@ -256,10 +259,16 @@ - (void) highlightSubviewsOfView: (UIView *) aView
256259
CFDictionarySetValue( _selectionColorInfo, view, info );
257260
}
258261

259-
id value = [view valueForKey: @"highlighted"];
260-
if ( value == nil )
261-
value = [NSNumber numberWithBool: NO];
262-
[info setObject: value forKey: @"highlighted"];
262+
// don't overwrite any prior cache of a view's original highlighted state.
263+
// this is because 'highlighted' will be set, then 'selected', which can perform 'highlight' again before the animation completes
264+
if ( [info objectForKey: @"highlighted"] == nil )
265+
{
266+
id value = [view valueForKey: @"highlighted"];
267+
if ( value == nil )
268+
value = [NSNumber numberWithBool: NO];
269+
[info setObject: value forKey: @"highlighted"];
270+
}
271+
263272
[view setValue: [NSNumber numberWithBool: YES]
264273
forKey: @"highlighted"];
265274
}
@@ -340,12 +349,6 @@ - (void) _beginBackgroundHighlight: (BOOL) highlightOn animated: (BOOL) animated
340349
}
341350
else
342351
{
343-
[UIView setAnimationsEnabled: NO];
344-
// find all non-opaque subviews and make opaque again, with original background colors
345-
[self makeSubviewsOfViewOpaqueAgain: self];
346-
[UIView setAnimationsEnabled: YES];
347-
348-
// now we're animating once more
349352
_selectedBackgroundView.alpha = 0.0;
350353
}
351354

@@ -392,6 +395,11 @@ - (void) highlightAnimationStopped: (NSString * __unused) animationID context: (
392395
}
393396
else
394397
{
398+
[UIView setAnimationsEnabled: NO];
399+
// find all non-opaque subviews and make opaque again, with original background colors
400+
[self makeSubviewsOfViewOpaqueAgain: self];
401+
[UIView setAnimationsEnabled: YES];
402+
395403
_cellFlags.highlighted = 0;
396404
[_selectedBackgroundView removeFromSuperview];
397405
CFDictionaryRemoveAllValues( _selectionColorInfo );
@@ -457,7 +465,7 @@ - (void) setHighlighted: (BOOL) value animated: (BOOL) animated
457465
else
458466
theLayer.shadowColor = [[UIColor darkGrayColor] CGColor];
459467

460-
theLayer.shadowRadius = 12.0;
468+
theLayer.shadowRadius = self.selectionGlowShadowRadius;
461469

462470
// add or remove the 'shadow' as appropriate
463471
if ( value )

Classes/AQGridViewData.m

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ - (id) copyWithZone: (NSZone *) zone
6565
theCopy->_layoutDirection = _layoutDirection;
6666
theCopy->_topPadding = _topPadding;
6767
theCopy->_bottomPadding = _bottomPadding;
68+
theCopy->_leftPadding = _leftPadding;
69+
theCopy->_rightPadding = _rightPadding;
6870
theCopy->_numberOfItems = _numberOfItems;
6971
theCopy->_reorderedIndex = _reorderedIndex;
7072
return ( theCopy );
@@ -96,7 +98,11 @@ - (NSUInteger) itemIndexForPoint: (CGPoint) point
9698
NSUInteger x = (NSUInteger)floorf(point.x);
9799
NSUInteger col = x / (NSUInteger)_actualCellSize.width;
98100

99-
return ( (row * [self numberOfItemsPerRow]) + col );
101+
NSUInteger result = (row * [self numberOfItemsPerRow]) + col;
102+
if ( result >= self.numberOfItems )
103+
result = NSNotFound;
104+
105+
return ( result );
100106
}
101107

102108
- (CGRect) cellRectForPoint: (CGPoint) point
@@ -145,8 +151,11 @@ - (CGSize) sizeForEntireGrid
145151
if ( _numberOfItems % numPerRow != 0 )
146152
numRows++;
147153

148-
return ( CGSizeMake(((CGFloat)ceilf(_actualCellSize.width * numPerRow)) + _leftPadding + _rightPadding,
149-
((CGFloat)ceilf((CGFloat)numRows * _actualCellSize.height)) + _topPadding + _bottomPadding) );
154+
CGFloat height = ( ((CGFloat)ceilf((CGFloat)numRows * _actualCellSize.height)) + _topPadding + _bottomPadding );
155+
if (height < _gridView.bounds.size.height)
156+
height = _gridView.bounds.size.height + 1;
157+
158+
return ( CGSizeMake(((CGFloat)ceilf(_actualCellSize.width * numPerRow)) + _leftPadding + _rightPadding, height) );
150159
}
151160

152161
- (NSUInteger) numberOfItemsPerRow

Classes/AQGridViewUpdateInfo.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
- (NSArray *) sortedReloadItems;
9999

100100
- (AQGridViewData *) newGridViewData;
101+
- (NSUInteger) numberOfItemsAfterUpdates;
101102

102103
- (NSUInteger) newIndexForOldIndex: (NSUInteger) oldIndex;
103104

0 commit comments

Comments
 (0)