PageRenderTime 47ms CodeModel.GetById 10ms RepoModel.GetById 1ms app.codeStats 0ms

/TeamTalk/IOSDuoduo/ThridFramework/AQ/AQGridView.m

https://gitlab.com/lisit1003/TTiOSClient
Objective C | 1603 lines | 1135 code | 330 blank | 138 comment | 269 complexity | 031b1e826fd5fae9699e1a91dd9ac172 MD5 | raw file
  1. /*
  2. * AQGridView.m
  3. * AQGridView
  4. *
  5. * Created by Jim Dovey on 10/2/2010.
  6. * Copyright 2010 Kobo Inc. All rights reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * Redistributions of source code must retain the above copyright notice,
  13. * this list of conditions and the following disclaimer.
  14. *
  15. * Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in the
  17. * documentation and/or other materials provided with the distribution.
  18. *
  19. * Neither the name of the project's author nor the names of its
  20. * contributors may be used to endorse or promote products derived from
  21. * this software without specific prior written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  24. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  25. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  26. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  27. * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  28. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  29. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  30. * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  31. * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  32. * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  33. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  34. *
  35. */
  36. #import "AQGridView.h"
  37. #import "AQGridViewUpdateItem.h"
  38. #import "AQGridViewAnimatorItem.h"
  39. #import "AQGridViewData.h"
  40. #import "AQGridViewUpdateInfo.h"
  41. #import "AQGridViewCell+AQGridViewCellPrivate.h"
  42. #import "AQGridView+CellLocationDelegation.h"
  43. #import "NSIndexSet+AQIsSetContiguous.h"
  44. #import "NSIndexSet+AQIndexesOutsideSet.h"
  45. #import <libkern/OSAtomic.h>
  46. // see _basicHitTest:withEvent: below
  47. #import <objc/objc.h>
  48. #import <objc/runtime.h>
  49. // Lightweight object class for touch selection parameters
  50. @interface UserSelectItemIndexParams : NSObject
  51. {
  52. NSUInteger _indexNum;
  53. NSUInteger _numFingers;
  54. };
  55. @property (nonatomic, assign) NSUInteger indexNum;
  56. @property (nonatomic, assign) NSUInteger numFingers;
  57. @end
  58. @implementation UserSelectItemIndexParams
  59. @synthesize indexNum = _indexNum;
  60. @synthesize numFingers = _numFingers;
  61. @end
  62. NSString * const AQGridViewSelectionDidChangeNotification = @"AQGridViewSelectionDidChangeNotification";
  63. @interface AQGridView (AQCellGridMath)
  64. - (NSUInteger) visibleCellListIndexForItemIndex: (NSUInteger) itemIndex;
  65. @end
  66. @interface AQGridView (AQCellLayout)
  67. - (void) layoutCellsInVisibleCellRange: (NSRange) range;
  68. - (void) layoutAllCells;
  69. - (CGRect) fixCellFrame: (CGRect) cellFrame forGridRect: (CGRect) gridRect;
  70. - (void) updateVisibleGridCellsNow;
  71. //- (void) updateForwardCellsForVisibleIndices: (NSIndexSet *) newVisibleIndices;
  72. - (AQGridViewCell *) createPreparedCellForIndex: (NSUInteger) index;
  73. - (void) insertVisibleCell: (AQGridViewCell *) cell atIndex: (NSUInteger) visibleCellListIndex;
  74. - (void) deleteVisibleCell: (AQGridViewCell *) cell atIndex: (NSUInteger) visibleCellListIndex appendingNewCell: (AQGridViewCell *) newLastCell;
  75. @end
  76. @interface AQGridView ()
  77. @property (nonatomic, copy) NSIndexSet * animatingIndices;
  78. @end
  79. @implementation AQGridView
  80. @synthesize dataSource=_dataSource, backgroundView=_backgroundView, separatorColor=_separatorColor, animatingCells=_animatingCells, animatingIndices=_animatingIndices;
  81. - (void) _sharedGridViewInit
  82. {
  83. _gridData = [[AQGridViewData alloc] initWithGridView: self];
  84. [_gridData setDesiredCellSize: CGSizeMake(96.0, 128.0)];
  85. _visibleBounds = self.bounds;
  86. _visibleCells = [[NSMutableArray alloc] init];
  87. _reusableGridCells = [[NSMutableDictionary alloc] init];
  88. _highlightedIndices = [[NSMutableIndexSet alloc] init];
  89. _updateInfoStack = [[NSMutableArray alloc] init];
  90. self.clipsToBounds = YES;
  91. self.separatorColor = [UIColor colorWithWhite: 0.85 alpha: 1.0];
  92. self.canCancelContentTouches = YES;
  93. _selectedIndex = NSNotFound;
  94. _pendingSelectionIndex = NSNotFound;
  95. _flags.resizesCellWidths = 0;
  96. _flags.numColumns = [_gridData numberOfItemsPerRow];
  97. _flags.separatorStyle = AQGridViewCellSeparatorStyleEmptySpace;
  98. _flags.allowsSelection = 1;
  99. _flags.usesPagedHorizontalScrolling = NO;
  100. _flags.contentSizeFillsBounds = 1;
  101. }
  102. - (id)initWithFrame: (CGRect) frame
  103. {
  104. self = [super initWithFrame:frame];
  105. if ( self == nil )
  106. return ( nil );
  107. [self _sharedGridViewInit];
  108. return ( self );
  109. }
  110. - (id) initWithCoder: (NSCoder *) aDecoder
  111. {
  112. self = [super initWithCoder: aDecoder];
  113. if ( self == nil )
  114. return ( nil );
  115. [self _sharedGridViewInit];
  116. return ( self );
  117. }
  118. /*
  119. // Only override drawRect: if you perform custom drawing.
  120. // An empty implementation adversely affects performance during animation.
  121. - (void)drawRect:(CGRect)rect {
  122. // Drawing code
  123. }
  124. */
  125. #pragma mark -
  126. #pragma mark Properties
  127. - (void) setDelegate: (id<AQGridViewDelegate>) obj
  128. {
  129. if ( (obj != nil) && ([obj conformsToProtocol: @protocol(AQGridViewDelegate)] == NO ))
  130. [NSException raise: NSInvalidArgumentException format: @"Argument to -setDelegate must conform to the AQGridViewDelegate protocol"];
  131. [super setDelegate: obj];
  132. _flags.delegateWillDisplayCell = [obj respondsToSelector: @selector(gridView:willDisplayCell:forItemAtIndex:)];
  133. _flags.delegateWillSelectItem = [obj respondsToSelector: @selector(gridView:willSelectItemAtIndex:)];
  134. _flags.delegateWillSelectItemMultiTouch = [obj respondsToSelector: @selector(gridView:willSelectItemAtIndex:numFingersTouch:)];
  135. _flags.delegateWillDeselectItem = [obj respondsToSelector: @selector(gridView:willDeselectItemAtIndex:)];
  136. _flags.delegateDidSelectItem = [obj respondsToSelector: @selector(gridView:didSelectItemAtIndex:)];
  137. _flags.delegateDidSelectItemMultiTouch = [obj respondsToSelector: @selector(gridView:didSelectItemAtIndex:numFingersTouch:)];
  138. _flags.delegateDidDeselectItem = [obj respondsToSelector: @selector(gridView:didDeselectItemAtIndex:)];
  139. _flags.delegateGestureRecognizerActivated = [obj respondsToSelector: @selector(gridView:gestureRecognizer:activatedForItemAtIndex:)];
  140. _flags.delegateAdjustGridCellFrame = [obj respondsToSelector: @selector(gridView:adjustCellFrame:withinGridCellFrame:)];
  141. _flags.delegateDidEndUpdateAnimation = [obj respondsToSelector:@selector(gridViewDidEndUpdateAnimation:)];
  142. }
  143. - (id<AQGridViewDelegate>) delegate
  144. {
  145. id obj = [super delegate];
  146. if ( [obj conformsToProtocol: @protocol(AQGridViewDelegate)] == NO )
  147. return ( nil );
  148. return ( obj );
  149. }
  150. - (void) setDataSource: (id<AQGridViewDataSource>) obj
  151. {
  152. if ((obj != nil) && ([obj conformsToProtocol: @protocol(AQGridViewDataSource)] == NO ))
  153. [NSException raise: NSInvalidArgumentException format: @"Argument to -setDataSource must conform to the AQGridViewDataSource protocol"];
  154. _dataSource = obj;
  155. _flags.dataSourceGridCellSize = [obj respondsToSelector: @selector(portraitGridCellSizeForGridView:)];
  156. }
  157. - (AQGridViewLayoutDirection) layoutDirection
  158. {
  159. return ( _gridData.layoutDirection );
  160. }
  161. - (void) setLayoutDirection: (AQGridViewLayoutDirection) direction
  162. {
  163. _gridData.layoutDirection = direction;
  164. }
  165. - (NSUInteger) numberOfItems
  166. {
  167. return ( _gridData.numberOfItems );
  168. }
  169. - (NSUInteger) numberOfColumns
  170. {
  171. if ( _flags.numColumns == 0 )
  172. _flags.numColumns = 1;
  173. return ( _flags.numColumns );
  174. }
  175. - (NSUInteger) numberOfRows
  176. {
  177. return ( _gridData.numberOfItems / _flags.numColumns );
  178. }
  179. - (BOOL) allowsSelection
  180. {
  181. return ( _flags.allowsSelection );
  182. }
  183. - (void) setAllowsSelection: (BOOL) value
  184. {
  185. _flags.allowsSelection = (value ? 1 : 0);
  186. }
  187. - (BOOL) backgroundViewExtendsDown
  188. {
  189. return ( _flags.backgroundViewExtendsDown);
  190. }
  191. - (void) setBackgroundViewExtendsDown: (BOOL) value
  192. {
  193. _flags.backgroundViewExtendsDown = (value ? 1 : 0);
  194. }
  195. - (BOOL) backgroundViewExtendsUp
  196. {
  197. return ( _flags.backgroundViewExtendsUp);
  198. }
  199. - (void) setBackgroundViewExtendsUp: (BOOL) value
  200. {
  201. _flags.backgroundViewExtendsUp = (value ? 1 : 0);
  202. }
  203. - (BOOL) requiresSelection
  204. {
  205. return ( _flags.requiresSelection );
  206. }
  207. - (void) setRequiresSelection: (BOOL) value
  208. {
  209. _flags.requiresSelection = (value ? 1 : 0);
  210. }
  211. - (BOOL) resizesCellWidthToFit
  212. {
  213. return ( _flags.resizesCellWidths );
  214. }
  215. - (void) setResizesCellWidthToFit: (BOOL) value
  216. {
  217. int i = (value ? 1 : 0);
  218. if ( _flags.resizesCellWidths == i )
  219. return;
  220. _flags.resizesCellWidths = i;
  221. [self setNeedsLayout];
  222. }
  223. - (BOOL) clipsContentWidthToBounds
  224. {
  225. return ( self.layoutDirection == AQGridViewLayoutDirectionVertical );
  226. }
  227. - (void) setClipsContentWidthToBounds: (BOOL) value
  228. {
  229. self.layoutDirection = (value ? AQGridViewLayoutDirectionVertical : AQGridViewLayoutDirectionHorizontal);
  230. }
  231. - (BOOL) usesPagedHorizontalScrolling
  232. {
  233. return ( _flags.usesPagedHorizontalScrolling );
  234. }
  235. - (void) setUsesPagedHorizontalScrolling: (BOOL) value
  236. {
  237. int i = (value ? 1 : 0);
  238. if ( _flags.usesPagedHorizontalScrolling == i )
  239. return;
  240. _flags.usesPagedHorizontalScrolling = i;
  241. [self setNeedsLayout];
  242. }
  243. - (AQGridViewCellSeparatorStyle) separatorStyle
  244. {
  245. return ( _flags.separatorStyle );
  246. }
  247. - (void) setSeparatorStyle: (AQGridViewCellSeparatorStyle) style
  248. {
  249. if ( style == _flags.separatorStyle )
  250. return;
  251. _flags.separatorStyle = style;
  252. for ( AQGridViewCell * cell in _visibleCells )
  253. {
  254. cell.separatorStyle = style;
  255. }
  256. [self setNeedsLayout];
  257. }
  258. - (CGFloat) leftContentInset
  259. {
  260. return ( _gridData.leftPadding );
  261. }
  262. - (void) setLeftContentInset: (CGFloat) inset
  263. {
  264. _gridData.leftPadding = inset;
  265. }
  266. - (CGFloat) rightContentInset
  267. {
  268. return ( _gridData.rightPadding );
  269. }
  270. - (void) setRightContentInset: (CGFloat) inset
  271. {
  272. _gridData.rightPadding = inset;
  273. }
  274. - (CGSize) gridCellSize
  275. {
  276. return ( [_gridData cellSize] );
  277. }
  278. - (UIView *) gridHeaderView
  279. {
  280. return ( _headerView );
  281. }
  282. - (void) setGridHeaderView: (UIView *) newHeaderView
  283. {
  284. if ( newHeaderView == _headerView )
  285. return;
  286. [_headerView removeFromSuperview];
  287. _headerView = newHeaderView;
  288. if ( _headerView == nil )
  289. {
  290. _gridData.topPadding = 0.0;
  291. }
  292. else
  293. {
  294. [self addSubview: _headerView];
  295. _gridData.topPadding = _headerView.frame.size.height;
  296. }
  297. [self setNeedsLayout];
  298. }
  299. - (UIView *) gridFooterView
  300. {
  301. return ( _footerView );
  302. }
  303. - (void) setGridFooterView: (UIView *) newFooterView
  304. {
  305. if ( newFooterView == _footerView )
  306. return;
  307. [_footerView removeFromSuperview];
  308. _footerView = newFooterView;
  309. if ( _footerView == nil )
  310. {
  311. _gridData.bottomPadding = 0.0;
  312. }
  313. else
  314. {
  315. [self addSubview: _footerView];
  316. _gridData.bottomPadding = _footerView.frame.size.height;
  317. }
  318. [self setNeedsLayout];
  319. }
  320. - (BOOL) contentSizeGrowsToFillBounds
  321. {
  322. return ( _flags.contentSizeFillsBounds == 1 );
  323. }
  324. - (void) setContentSizeGrowsToFillBounds: (BOOL) value
  325. {
  326. _flags.contentSizeFillsBounds = (value ? 1 : 0);
  327. }
  328. - (void) setAnimatingCells: (NSSet *) set
  329. {
  330. _animatingCells = set;
  331. NSMutableIndexSet * indices = [[NSMutableIndexSet alloc] init];
  332. for ( AQGridViewAnimatorItem * item in set )
  333. {
  334. if ( item.index != NSNotFound )
  335. [indices addIndex: item.index];
  336. }
  337. self.animatingIndices = indices;
  338. }
  339. - (BOOL) isAnimatingUpdates
  340. {
  341. return ( _animationCount > 0 );
  342. }
  343. - (void) updateContentRectWithOldMaxLocation: (CGPoint) oldMaxLocation gridSize: (CGSize) gridSize
  344. {
  345. // The following line prevents an update leading to unneccessary auto-scrolling
  346. // Before this fix, AQGridView animation always caused scrolling to the most bottom line
  347. if (CGSizeEqualToSize(self.contentSize, gridSize)) return;
  348. // update content size
  349. self.contentSize = gridSize;
  350. // fix content offset if applicable
  351. CGPoint offset = self.contentOffset;
  352. CGPoint oldOffset = offset;
  353. if ( offset.y + self.bounds.size.height > gridSize.height )
  354. {
  355. offset.y = MAX(0.0, self.contentSize.height - self.bounds.size.height);
  356. }
  357. else if ( !CGPointEqualToPoint(oldOffset, CGPointZero) ) // stick-to-top takes precedence
  358. {
  359. if ( [_gridData pointIsInLastRow: oldMaxLocation] )
  360. {
  361. // we were scrolled to the bottom-- stay there as our height decreases
  362. if ( self.layoutDirection == AQGridViewLayoutDirectionVertical )
  363. offset.y = MAX(0.0, self.contentSize.height - self.bounds.size.height);
  364. else
  365. offset.x = MAX(0.0, self.contentSize.width - self.bounds.size.width);
  366. }
  367. }
  368. //NSLog( @"Resetting offset from %@ to %@", NSStringFromCGPoint(oldOffset), NSStringFromCGPoint(offset) );
  369. self.contentOffset = offset;
  370. }
  371. - (void) handleGridViewBoundsChanged: (CGRect) oldBounds toNewBounds: (CGRect) bounds
  372. {
  373. CGSize oldGridSize = [_gridData sizeForEntireGrid];
  374. BOOL wasAtBottom = ((oldGridSize.height != 0.0) && (CGRectGetMaxY(oldBounds) == oldGridSize.height));
  375. [_gridData gridViewDidChangeBoundsSize: bounds.size];
  376. _flags.numColumns = [_gridData numberOfItemsPerRow];
  377. CGSize newGridSize = [_gridData sizeForEntireGrid];
  378. CGPoint oldMaxLocation = CGPointMake(CGRectGetMaxX(oldBounds), CGRectGetMaxY(oldBounds));
  379. [self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: newGridSize];
  380. if ( (wasAtBottom) && (!CGPointEqualToPoint(oldBounds.origin, CGPointZero)) && (newGridSize.height > oldGridSize.height) )
  381. {
  382. CGRect contentRect = self.bounds;
  383. if ( CGRectGetMaxY(contentRect) < newGridSize.height )
  384. {
  385. contentRect.origin.y += (newGridSize.height - oldGridSize.height);
  386. self.contentOffset = contentRect.origin;
  387. }
  388. }
  389. [self updateVisibleGridCellsNow];
  390. _flags.allCellsNeedLayout = 1;
  391. }
  392. - (void) setContentOffset:(CGPoint) offset
  393. {
  394. [super setContentOffset: offset];
  395. }
  396. - (void)setContentOffset: (CGPoint) contentOffset animated: (BOOL) animate
  397. {
  398. // Call our super duper method
  399. [super setContentOffset: contentOffset animated: animate];
  400. // for long grids, ensure there are visible cells when scrolled to
  401. if (!animate)
  402. {
  403. [self updateVisibleGridCellsNow];
  404. /*if (![_visibleCells count])
  405. {
  406. NSIndexSet * newIndices = [_gridData indicesOfCellsInRect: [self gridViewVisibleBounds]];
  407. [self updateForwardCellsForVisibleIndices: newIndices];
  408. }*/
  409. }
  410. }
  411. - (void) setContentSize: (CGSize) newSize
  412. {
  413. if ( (_flags.contentSizeFillsBounds == 1) && (newSize.height < self.bounds.size.height) )
  414. newSize.height = self.bounds.size.height;
  415. if (self.gridFooterView)
  416. {
  417. // In-call status bar influences footer position
  418. CGRect statusRect = [UIApplication sharedApplication].statusBarFrame;
  419. CGFloat statusHeight = MIN(CGRectGetWidth(statusRect), CGRectGetHeight(statusRect)) - 20;
  420. CGFloat footerHeight = CGRectGetHeight(self.gridFooterView.bounds);
  421. CGFloat minimumHeight = statusHeight + CGRectGetHeight(self.bounds) + footerHeight;
  422. if (newSize.height < footerHeight + minimumHeight)
  423. newSize.height = minimumHeight;
  424. }
  425. newSize.height = fmax(newSize.height, self.frame.size.height);
  426. CGSize oldSize = self.contentSize;
  427. [super setContentSize: newSize];
  428. if ( oldSize.width != newSize.width )
  429. [_gridData gridViewDidChangeBoundsSize: newSize];
  430. if ( CGRectGetMaxY(self.bounds) > newSize.height )
  431. {
  432. CGRect b = self.bounds;
  433. CGFloat diff = CGRectGetMaxY(b) - newSize.height;
  434. b.origin.y = MAX(0.0, b.origin.y - diff);
  435. self.bounds = b;
  436. }
  437. }
  438. - (void) setFrame: (CGRect) newFrame
  439. {
  440. CGRect oldBounds = self.bounds;
  441. [super setFrame: newFrame];
  442. CGRect newBounds = self.bounds;
  443. if ( newBounds.size.width != oldBounds.size.width )
  444. [self handleGridViewBoundsChanged: oldBounds toNewBounds: newBounds];
  445. }
  446. - (void) setBounds: (CGRect) bounds
  447. {
  448. CGRect oldBounds = self.bounds;
  449. [super setBounds: bounds];
  450. bounds = self.bounds; // in case it was modified
  451. if ( !CGSizeEqualToSize(bounds.size, oldBounds.size) )
  452. [self handleGridViewBoundsChanged: oldBounds toNewBounds: bounds];
  453. }
  454. - (BOOL) isEditing
  455. {
  456. return ( _flags.isEditing == 1 );
  457. }
  458. - (void) setEditing: (BOOL) value
  459. {
  460. [self setEditing:value animated:NO];
  461. }
  462. #pragma mark -
  463. #pragma mark Data Management
  464. - (AQGridViewCell *) dequeueReusableCellWithIdentifier: (NSString *) reuseIdentifier
  465. {
  466. NSMutableSet * cells = [_reusableGridCells objectForKey: reuseIdentifier];
  467. AQGridViewCell * cell = [cells anyObject];
  468. if ( cell == nil )
  469. return ( nil );
  470. [cell prepareForReuse];
  471. [cells removeObject: cell];
  472. return ( cell );
  473. }
  474. - (void) enqueueReusableCells: (NSArray *) reusableCells
  475. {
  476. for ( AQGridViewCell * cell in reusableCells )
  477. {
  478. NSMutableSet * reuseSet = [_reusableGridCells objectForKey: cell.reuseIdentifier];
  479. if ( reuseSet == nil )
  480. {
  481. reuseSet = [[NSMutableSet alloc] initWithCapacity: 32];
  482. [_reusableGridCells setObject: reuseSet forKey: cell.reuseIdentifier];
  483. }
  484. else if ( [reuseSet member: cell] == cell )
  485. {
  486. NSLog( @"Warning: tried to add duplicate gridview cell" );
  487. continue;
  488. }
  489. [reuseSet addObject: cell];
  490. }
  491. }
  492. - (CGRect) gridViewVisibleBounds
  493. {
  494. CGRect result = CGRectZero;
  495. result.origin = self.contentOffset;
  496. result.size = self.bounds.size;
  497. return ( result );
  498. }
  499. - (void) reloadData
  500. {
  501. if ( _reloadingSuspendedCount != 0 )
  502. return;
  503. if ( _flags.dataSourceGridCellSize == 1 )
  504. {
  505. [_gridData setDesiredCellSize: [_dataSource portraitGridCellSizeForGridView: self]];
  506. _flags.numColumns = [_gridData numberOfItemsPerRow];
  507. }
  508. _gridData.numberOfItems = [_dataSource numberOfItemsInGridView: self];
  509. // update our content size as appropriate
  510. self.contentSize = [_gridData sizeForEntireGrid];
  511. // fix up the visible index list
  512. NSUInteger cutoff = MAX(0, _gridData.numberOfItems-_visibleIndices.length);
  513. _visibleIndices.location = MIN(_visibleIndices.location, cutoff);
  514. _visibleIndices.length = 0;
  515. // remove all existing cells
  516. [_visibleCells makeObjectsPerformSelector: @selector(removeFromSuperview)];
  517. [self enqueueReusableCells: _visibleCells];
  518. [_visibleCells removeAllObjects];
  519. // -layoutSubviews will update the visible cell list
  520. // layout -- no animation
  521. [self setNeedsLayout];
  522. _flags.allCellsNeedLayout = 1;
  523. }
  524. #define MAX_BOUNCE_DISTANCE (500.0f)
  525. - (void) layoutSubviews
  526. {
  527. if ( (_flags.needsReload == 1) && (_animationCount == 0) && (_reloadingSuspendedCount == 0) )
  528. [self reloadData];
  529. if ( (_reloadingSuspendedCount == 0) && (!CGRectIsEmpty([self gridViewVisibleBounds])) )
  530. {
  531. [self updateVisibleGridCellsNow];
  532. }
  533. if ( _flags.allCellsNeedLayout == 1 )
  534. {
  535. _flags.allCellsNeedLayout = 0;
  536. if ( _visibleIndices.length != 0 )
  537. [self layoutAllCells];
  538. }
  539. CGRect rect = CGRectZero;
  540. rect.size.width = self.bounds.size.width;
  541. rect.size.height = self.contentSize.height - (_gridData.topPadding + _gridData.bottomPadding);
  542. rect.origin.y += _gridData.topPadding;
  543. // Make sure background is an integral number of rows tall. That way, it draws patterned colours correctly on all OSes.
  544. CGRect backgroundRect = rect;
  545. if ([self backgroundViewExtendsUp]) {
  546. backgroundRect.origin.y = backgroundRect.origin.y - MAX_BOUNCE_DISTANCE;
  547. backgroundRect.size.height += MAX_BOUNCE_DISTANCE; // don't just move it, grow it
  548. }
  549. if ([self backgroundViewExtendsDown]) {
  550. backgroundRect.size.height = backgroundRect.size.height + MAX_BOUNCE_DISTANCE;
  551. }
  552. CGFloat minimumHeight = rect.size.height,
  553. actualHeight = 0;
  554. if (([_gridData numberOfItems] == 0) || ([_gridData numberOfItemsPerRow] == 0)) {
  555. actualHeight = [_gridData cellSize].height;
  556. } else {
  557. actualHeight = [_gridData cellSize].height * ([_gridData numberOfItems] / [_gridData numberOfItemsPerRow] + 1);
  558. }
  559. for (; actualHeight < minimumHeight; actualHeight += [_gridData cellSize].height) {
  560. }
  561. backgroundRect.size.height = actualHeight;
  562. self.backgroundView.frame = backgroundRect;
  563. if ( _headerView != nil )
  564. {
  565. rect = _headerView.frame;
  566. rect.origin = CGPointZero;
  567. rect.size.width = self.bounds.size.width;
  568. _headerView.frame = rect;
  569. }
  570. if ( _footerView != nil )
  571. {
  572. rect = _footerView.frame;
  573. rect.origin.x = 0.0;
  574. rect.origin.y = self.contentSize.height - rect.size.height;
  575. rect.size.width = self.bounds.size.width;
  576. _footerView.frame = rect;
  577. [self bringSubviewToFront:_footerView];
  578. }
  579. }
  580. - (CGRect) rectForItemAtIndex: (NSUInteger) index
  581. {
  582. // simple case -- there's a cell already, we can just ask for its frame
  583. if ( NSLocationInRange(index, _visibleIndices) )
  584. return ( [[_visibleCells objectAtIndex: [self visibleCellListIndexForItemIndex: index]] frame] );
  585. // complex case-- compute the frame manually
  586. return ( [self fixCellFrame: CGRectZero forGridRect: [_gridData cellRectAtIndex: index]] );
  587. }
  588. - (AQGridViewCell *) cellForItemAtIndex: (NSUInteger) index
  589. {
  590. //if ( NSLocationInRange(index, _visibleIndices) == NO )
  591. // return ( nil );
  592. // we don't clip to visible range-- when animating edits the visible cell list can contain extra items
  593. NSUInteger visibleCellListIndex = [self visibleCellListIndexForItemIndex: index];
  594. if ( visibleCellListIndex < [_visibleCells count] )
  595. return ( [_visibleCells objectAtIndex: visibleCellListIndex] );
  596. return ( nil );
  597. }
  598. - (NSUInteger) indexForItemAtPoint: (CGPoint) point
  599. {
  600. return ( [_gridData itemIndexForPoint: point] );
  601. }
  602. - (NSUInteger) indexForCell: (AQGridViewCell *) cell
  603. {
  604. NSUInteger index = [_visibleCells indexOfObject:cell];
  605. if (index == NSNotFound)
  606. return NSNotFound;
  607. return _visibleIndices.location + index;
  608. }
  609. - (AQGridViewCell *) cellForItemAtPoint: (CGPoint) point
  610. {
  611. return ( [self cellForItemAtIndex: [_gridData itemIndexForPoint: point]] );
  612. }
  613. - (NSArray *) visibleCells
  614. {
  615. return ( [_visibleCells copy] );
  616. }
  617. - (NSIndexSet *) visibleCellIndices
  618. {
  619. return ( [NSIndexSet indexSetWithIndexesInRange: _visibleIndices] );
  620. }
  621. - (void) scrollToItemAtIndex: (NSUInteger) index atScrollPosition: (AQGridViewScrollPosition) scrollPosition
  622. animated: (BOOL) animated
  623. {
  624. CGRect gridRect = [_gridData cellRectAtIndex: index];
  625. CGRect targetRect = self.bounds;
  626. switch ( scrollPosition )
  627. {
  628. case AQGridViewScrollPositionNone:
  629. default:
  630. targetRect = gridRect; // no special coordinate handling
  631. break;
  632. case AQGridViewScrollPositionTop:
  633. targetRect.origin.y = gridRect.origin.y; // set target y origin to cell's y origin
  634. break;
  635. case AQGridViewScrollPositionMiddle:
  636. targetRect.origin.y = MAX(gridRect.origin.y - (CGFloat)ceilf((targetRect.size.height - gridRect.size.height) * 0.5), 0.0);
  637. break;
  638. case AQGridViewScrollPositionBottom:
  639. targetRect.origin.y = MAX((CGFloat)floorf(gridRect.origin.y - (targetRect.size.height - gridRect.size.height)), 0.0);
  640. break;
  641. }
  642. [self scrollRectToVisible: targetRect animated: animated];
  643. // for long grids, ensure there are visible cells when scrolled to
  644. if (!animated) {
  645. [self updateVisibleGridCellsNow];
  646. /*if (![_visibleCells count]) {
  647. NSIndexSet * newIndices = [_gridData indicesOfCellsInRect: [self gridViewVisibleBounds]];
  648. [self updateForwardCellsForVisibleIndices: newIndices];
  649. }*/
  650. }
  651. }
  652. #pragma mark -
  653. #pragma mark Cell Updates
  654. - (BOOL) isRectVisible: (CGRect) frameRect
  655. {
  656. return ( CGRectIntersectsRect(frameRect, self.bounds) );
  657. }
  658. - (void) fixCellsFromAnimation
  659. {
  660. // the visible cell list might contain hidden cells-- make them visible now
  661. for ( AQGridViewCell * cell in _visibleCells )
  662. {
  663. if ( cell.hiddenForAnimation )
  664. {
  665. cell.hiddenForAnimation = NO;
  666. if ( _flags.delegateWillDisplayCell == 1 )
  667. [self delegateWillDisplayCell: cell atIndex: cell.displayIndex];
  668. cell.hidden = NO;
  669. }
  670. }
  671. // update the visible item list appropriately
  672. NSIndexSet * indices = [_gridData indicesOfCellsInRect: self.bounds];
  673. if ( [indices count] == 0 )
  674. {
  675. _visibleIndices.location = 0;
  676. _visibleIndices.length = 0;
  677. [_visibleCells makeObjectsPerformSelector: @selector(removeFromSuperview)];
  678. [self enqueueReusableCells: _visibleCells];
  679. [_visibleCells removeAllObjects];
  680. // update the content size/offset based on the new grid data
  681. CGPoint oldMaxLocation = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
  682. [self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: [_gridData sizeForEntireGrid]];
  683. return;
  684. }
  685. _visibleIndices.location = [indices firstIndex];
  686. _visibleIndices.length = [indices count];
  687. NSMutableArray * newVisibleCells = [[NSMutableArray alloc] initWithCapacity: _visibleIndices.length];
  688. for ( AQGridViewAnimatorItem * item in self.animatingCells )
  689. {
  690. if ( [item.animatingView isKindOfClass: [AQGridViewCell class]] == NO )
  691. {
  692. [item.animatingView removeFromSuperview];
  693. continue;
  694. }
  695. if ( [self isRectVisible: [_gridData cellRectForPoint: item.animatingView.center]] == NO )
  696. {
  697. [item.animatingView removeFromSuperview];
  698. continue;
  699. }
  700. [newVisibleCells addObject: item.animatingView];
  701. }
  702. //NSAssert([newVisibleCells count] == _visibleIndices.length, @"visible cell count after animation doesn't match visible indices");
  703. [newVisibleCells sortUsingSelector: @selector(compareOriginAgainstCell:)];
  704. [_visibleCells removeObjectsInArray: newVisibleCells];
  705. [_visibleCells makeObjectsPerformSelector: @selector(removeFromSuperview)];
  706. [_visibleCells setArray: newVisibleCells];
  707. self.animatingCells = nil;
  708. NSMutableSet * removals = [[NSMutableSet alloc] init];
  709. for ( UIView * view in self.subviews )
  710. {
  711. if ( [view isKindOfClass: [AQGridViewCell class]] == NO )
  712. continue;
  713. if ( [_visibleCells containsObject: view] == NO )
  714. [removals addObject: view];
  715. }
  716. [removals makeObjectsPerformSelector: @selector(removeFromSuperview)];
  717. // update the content size/offset based on the new grid data
  718. CGPoint oldMaxLocation = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
  719. [self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: [_gridData sizeForEntireGrid]];
  720. }
  721. - (void) setupUpdateAnimations
  722. {
  723. _reloadingSuspendedCount++;
  724. AQGridViewUpdateInfo * info = [[AQGridViewUpdateInfo alloc] initWithOldGridData: _gridData forGridView: self];
  725. [_updateInfoStack addObject: info];
  726. }
  727. - (void) endUpdateAnimations
  728. {
  729. NSAssert([_updateInfoStack lastObject] != nil, @"_updateInfoStack should not be empty at this point" );
  730. __block AQGridViewUpdateInfo * info = [_updateInfoStack lastObject];
  731. if ( info.numberOfUpdates == 0 )
  732. {
  733. [_updateInfoStack removeObject: info];
  734. _reloadingSuspendedCount--;
  735. return;
  736. }
  737. NSUInteger expectedItemCount = [info numberOfItemsAfterUpdates];
  738. NSUInteger actualItemCount = [_dataSource numberOfItemsInGridView: self];
  739. if ( expectedItemCount != actualItemCount )
  740. {
  741. NSUInteger numAdded = [[info sortedInsertItems] count];
  742. NSUInteger numDeleted = [[info sortedDeleteItems] count];
  743. [_updateInfoStack removeObject: info];
  744. _reloadingSuspendedCount--;
  745. [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];
  746. }
  747. [info cleanupUpdateItems];
  748. _animationCount++;
  749. //NSAssert(_animationCount == 1, @"Stacked animations occurring!!");
  750. [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionCurveEaseInOut
  751. animations:^(void) {
  752. self.animatingCells = [info animateCellUpdatesUsingVisibleContentRect: [self gridViewVisibleBounds]];
  753. _gridData = [info newGridViewData];
  754. if ( _selectedIndex != NSNotFound )
  755. _selectedIndex = [info newIndexForOldIndex: _selectedIndex];
  756. _reloadingSuspendedCount--;
  757. }
  758. completion:^(BOOL finished) {
  759. // if nothing was animated, we don't have to do anything at all
  760. // if ( self.animatingCells.count != 0 )
  761. [self fixCellsFromAnimation];
  762. // NB: info becomes invalid at this point
  763. [_updateInfoStack removeObject: info];
  764. _animationCount--;
  765. //_reloadingSuspendedCount--;
  766. if ( _flags.delegateDidEndUpdateAnimation == 1 )
  767. [self.delegate gridViewDidEndUpdateAnimation: self];
  768. }];
  769. }
  770. - (void) beginUpdates
  771. {
  772. if ( _updateCount++ == 0 )
  773. [self setupUpdateAnimations];
  774. }
  775. - (void) endUpdates
  776. {
  777. if ( --_updateCount == 0 )
  778. [self endUpdateAnimations];
  779. }
  780. - (void) _updateItemsAtIndices: (NSIndexSet *) indices updateAction: (AQGridViewUpdateAction) action withAnimation: (AQGridViewItemAnimation) animation
  781. {
  782. BOOL needsAnimationSetup = ([_updateInfoStack count] <= _animationCount);
  783. // not in the middle of an update loop -- start animations here
  784. if ( needsAnimationSetup )
  785. [self setupUpdateAnimations];
  786. [[_updateInfoStack lastObject] updateItemsAtIndices: indices updateAction: action withAnimation: animation];
  787. // not in the middle of an update loop -- commit animations here
  788. if ( needsAnimationSetup )
  789. [self endUpdateAnimations];
  790. }
  791. - (void) insertItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation
  792. {
  793. [self _updateItemsAtIndices: indices updateAction: AQGridViewUpdateActionInsert withAnimation: animation];
  794. }
  795. - (void) deleteItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation
  796. {
  797. [self _updateItemsAtIndices: indices updateAction: AQGridViewUpdateActionDelete withAnimation: animation];
  798. }
  799. - (void) reloadItemsAtIndices: (NSIndexSet *) indices withAnimation: (AQGridViewItemAnimation) animation
  800. {
  801. [self _updateItemsAtIndices: indices updateAction: AQGridViewUpdateActionReload withAnimation: animation];
  802. }
  803. - (void) moveItemAtIndex: (NSUInteger) index toIndex: (NSUInteger) newIndex withAnimation: (AQGridViewItemAnimation) animation
  804. {
  805. BOOL needsAnimationSetup = ([_updateInfoStack count] <= _animationCount);
  806. if ( needsAnimationSetup )
  807. [self setupUpdateAnimations];
  808. [[_updateInfoStack lastObject] moveItemAtIndex: index toIndex: newIndex withAnimation: animation];
  809. if ( needsAnimationSetup )
  810. [self endUpdateAnimations];
  811. }
  812. - (void)setEditing:(BOOL)editing animated:(BOOL)animated
  813. {
  814. _flags.isEditing = (editing ? 1 : 0);
  815. NSArray *visibleCells = [self visibleCells];
  816. for (AQGridViewCell *aCell in visibleCells) {
  817. [aCell setEditing:editing animated:animated];
  818. }
  819. }
  820. #pragma mark -
  821. #pragma mark Selection
  822. - (NSUInteger) indexOfSelectedItem
  823. {
  824. return ( _selectedIndex );
  825. }
  826. - (void) highlightItemAtIndex: (NSUInteger) index animated: (BOOL) animated scrollPosition: (AQGridViewScrollPosition) position
  827. {
  828. if ( [_highlightedIndices containsIndex: index] )
  829. {
  830. if ( position != AQGridViewScrollPositionNone )
  831. [self scrollToItemAtIndex: index atScrollPosition: position animated: animated];
  832. return;
  833. }
  834. if ( index == NSNotFound )
  835. {
  836. NSUInteger i = [_highlightedIndices firstIndex];
  837. while ( i != NSNotFound )
  838. {
  839. AQGridViewCell * cell = [self cellForItemAtIndex: i];
  840. [cell setHighlighted: NO animated: animated];
  841. i = [_highlightedIndices indexGreaterThanIndex: i];
  842. }
  843. [_highlightedIndices removeAllIndexes];
  844. return;
  845. }
  846. AQGridViewCell * cell = [self cellForItemAtIndex: index];
  847. [cell setHighlighted: YES animated: animated];
  848. [_highlightedIndices addIndex: index];
  849. if ( position != AQGridViewScrollPositionNone )
  850. [self scrollToItemAtIndex: index atScrollPosition: position animated: animated];
  851. }
  852. - (void) unhighlightItemAtIndex: (NSUInteger) index animated: (BOOL) animated
  853. {
  854. if ( [_highlightedIndices containsIndex: index] == NO )
  855. return;
  856. [_highlightedIndices removeIndex: index];
  857. // don't remove highlighting if the cell is actually the selected cell
  858. if ( index == _selectedIndex )
  859. return;
  860. AQGridViewCell * cell = [self cellForItemAtIndex: index];
  861. if ( cell != nil )
  862. [cell setHighlighted: NO animated: animated];
  863. }
  864. - (void) _deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated notifyDelegate: (BOOL) notifyDelegate
  865. {
  866. if ( _selectedIndex != index )
  867. return;
  868. if ( notifyDelegate && _flags.delegateWillDeselectItem )
  869. [self.delegate gridView: self willDeselectItemAtIndex: index];
  870. _selectedIndex = NSNotFound;
  871. [[self cellForItemAtIndex: index] setSelected: NO animated: animated];
  872. if ( notifyDelegate && _flags.delegateDidDeselectItem )
  873. [self.delegate gridView: self didDeselectItemAtIndex: index];
  874. if ( notifyDelegate )
  875. {
  876. [[NSNotificationCenter defaultCenter] postNotificationName: AQGridViewSelectionDidChangeNotification
  877. object: self];
  878. }
  879. }
  880. - (void) _selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
  881. scrollPosition: (AQGridViewScrollPosition) position notifyDelegate: (BOOL) notifyDelegate
  882. numFingersTouch: (NSUInteger) numFingers
  883. {
  884. if ( _selectedIndex == index )
  885. return; // already selected this item
  886. if ( _selectedIndex != NSNotFound )
  887. [self _deselectItemAtIndex: _selectedIndex animated: animated notifyDelegate: notifyDelegate];
  888. if ( _flags.allowsSelection == 0 )
  889. return;
  890. if ( notifyDelegate && _flags.delegateWillSelectItem )
  891. index = [self.delegate gridView: self willSelectItemAtIndex: index];
  892. if ( notifyDelegate && _flags.delegateWillSelectItemMultiTouch )
  893. index = [self.delegate gridView: self willSelectItemAtIndex: index
  894. numFingersTouch:numFingers];
  895. _selectedIndex = index;
  896. [[self cellForItemAtIndex: index] setSelected: YES animated: animated];
  897. if ( position != AQGridViewScrollPositionNone )
  898. [self scrollToItemAtIndex: index atScrollPosition: position animated: animated];
  899. if ( notifyDelegate )
  900. {
  901. [[NSNotificationCenter defaultCenter] postNotificationName: AQGridViewSelectionDidChangeNotification
  902. object: self];
  903. }
  904. if ( notifyDelegate && _flags.delegateDidSelectItem )
  905. [self.delegate gridView: self didSelectItemAtIndex: index];
  906. if ( notifyDelegate && _flags.delegateDidSelectItemMultiTouch )
  907. [self.delegate gridView: self didSelectItemAtIndex: index numFingersTouch:numFingers];
  908. // ensure that the selected item is no longer marked as just 'highlighted' (that's an intermediary state)
  909. [_highlightedIndices removeIndex: index];
  910. }
  911. - (void) selectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
  912. scrollPosition: (AQGridViewScrollPosition) scrollPosition
  913. {
  914. [self _selectItemAtIndex: index animated: animated scrollPosition: scrollPosition notifyDelegate: NO
  915. numFingersTouch: 1];
  916. }
  917. - (void) deselectItemAtIndex: (NSUInteger) index animated: (BOOL) animated
  918. {
  919. [self _deselectItemAtIndex: index animated: animated notifyDelegate: NO];
  920. }
  921. #pragma mark -
  922. #pragma mark Appearance
  923. - (UIView *) backgroundView
  924. {
  925. return ( _backgroundView );
  926. }
  927. - (void) setBackgroundView: (UIView *) newView
  928. {
  929. if ( newView == _backgroundView )
  930. return;
  931. [_backgroundView removeFromSuperview];
  932. _backgroundView = newView;
  933. _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
  934. CGRect frame = self.bounds;
  935. frame.size = self.contentSize;
  936. CGRect backgroundRect = CGRectMake(0.0f, 0.0f, self.bounds.size.width, self.bounds.size.height);
  937. if ([self backgroundViewExtendsUp]) {
  938. backgroundRect.origin.y = backgroundRect.origin.y - MAX_BOUNCE_DISTANCE;
  939. backgroundRect.size.height += MAX_BOUNCE_DISTANCE; // don't just move it, grow it
  940. }
  941. if ([self backgroundViewExtendsDown]) {
  942. backgroundRect.size.height = backgroundRect.size.height + MAX_BOUNCE_DISTANCE;
  943. }
  944. _backgroundView.frame = backgroundRect;
  945. [self insertSubview: _backgroundView atIndex: 0];
  946. // this view is already laid out nicely-- no need to call -setNeedsLayout at all
  947. }
  948. - (UIColor *) separatorColor
  949. {
  950. return ( _separatorColor );
  951. }
  952. - (void) setSeparatorColor: (UIColor *) color
  953. {
  954. if ( color == _separatorColor )
  955. return;
  956. _separatorColor = color;
  957. for ( AQGridViewCell * cell in _visibleCells )
  958. {
  959. cell.separatorColor = _separatorColor;
  960. }
  961. }
  962. #pragma mark -
  963. #pragma mark Touch Events
  964. - (UIView *) _basicHitTest: (CGPoint) point withEvent: (UIEvent *) event
  965. {
  966. // STUPID STUPID RAT CREATURES
  967. // ===========================
  968. //
  969. // Problem: we want to do a default hit-test without UIScrollView's processing getting in the way.
  970. // UIScrollView implements _defaultHitTest:withEvent: for this, but we can't call that due to it
  971. // being a private API.
  972. // Instead, we have to manufacture a call to our super-super class here, grr
  973. Method method = class_getInstanceMethod( [UIView class], @selector(hitTest:withEvent:) );
  974. IMP imp = method_getImplementation( method );
  975. return ( (UIView *)imp(self, @selector(hitTest:withEvent:), point, event) ); // -[UIView hitTest:withEvent:]
  976. }
  977. - (BOOL) _canSelectItemContainingHitView: (UIView *) hitView
  978. {
  979. if ( [hitView isKindOfClass: [UIControl class]] )
  980. return ( NO );
  981. // Simply querying the superview will not work if the hit view is a subview of the contentView, e.g. its superview is a plain UIView *inside* a cell
  982. if ( [[hitView superview] isKindOfClass: [AQGridViewCell class]] )
  983. return ( YES );
  984. if ( [hitView isKindOfClass: [AQGridViewCell class]] )
  985. return ( YES );
  986. CGPoint hitCenter = [self convertPoint:[hitView center] fromView:hitView];
  987. for ( AQGridViewCell *aCell in [[self visibleCells] copy])
  988. {
  989. if ( CGRectContainsPoint( aCell.frame, hitCenter ) )
  990. return ( YES );
  991. }
  992. return ( NO );
  993. }
  994. - (void) _gridViewDeferredTouchesBegan: (NSNumber *) indexNum
  995. {
  996. if ( (self.dragging == NO) && (_flags.ignoreTouchSelect == 0) && (_pendingSelectionIndex != NSNotFound) )
  997. [self highlightItemAtIndex: _pendingSelectionIndex animated: NO scrollPosition: AQGridViewScrollPositionNone];
  998. //_pendingSelectionIndex = NSNotFound;
  999. }
  1000. - (void) _userSelectItemAtIndex: (UserSelectItemIndexParams*) params
  1001. {
  1002. NSUInteger index = params.indexNum;
  1003. NSUInteger numFingersCount = params.numFingers;
  1004. [self unhighlightItemAtIndex: index animated: NO];
  1005. if ( ([[self cellForItemAtIndex: index] isSelected]) && (self.requiresSelection == NO) )
  1006. [self _deselectItemAtIndex: index animated: NO notifyDelegate: YES];
  1007. else
  1008. [self _selectItemAtIndex: index animated: NO scrollPosition: AQGridViewScrollPositionNone notifyDelegate: YES
  1009. numFingersTouch: numFingersCount];
  1010. _pendingSelectionIndex = NSNotFound;
  1011. }
  1012. - (BOOL) _gestureRecognizerIsHandlingTouches: (NSSet *) touches
  1013. {
  1014. // see if the touch is (possibly) being tracked by a gesture recognizer
  1015. for ( UIGestureRecognizer *recognizer in self.gestureRecognizers )
  1016. {
  1017. switch ( [recognizer state] )
  1018. {
  1019. case UIGestureRecognizerStateEnded:
  1020. case UIGestureRecognizerStateCancelled:
  1021. case UIGestureRecognizerStateFailed:
  1022. continue;
  1023. default:
  1024. break;
  1025. }
  1026. if ( [recognizer numberOfTouches] == [touches count] )
  1027. {
  1028. // simple version:
  1029. // pick a touch from our event's set, and see if it's in the recognizer's set
  1030. UITouch * touch = [touches anyObject];
  1031. CGPoint touchLocation = [touch locationInView: self];
  1032. for ( NSUInteger i = 0; i < [recognizer numberOfTouches]; i++ )
  1033. {
  1034. CGPoint test = [recognizer locationOfTouch: i inView: self];
  1035. if ( CGPointEqualToPoint(test, touchLocation) )
  1036. {
  1037. return ( YES );
  1038. }
  1039. }
  1040. }
  1041. }
  1042. return ( NO );
  1043. }
  1044. - (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
  1045. {
  1046. _flags.ignoreTouchSelect = ([self isDragging] ? 1 : 0);
  1047. UITouch * touch = [touches anyObject];
  1048. _touchBeganPosition = [touch locationInView: nil];
  1049. if ( (touch != nil) && (_pendingSelectionIndex == NSNotFound) )
  1050. {
  1051. CGPoint pt = [touch locationInView: self];
  1052. UIView * hitView = [self _basicHitTest: pt withEvent: event];
  1053. _touchedContentView = hitView;
  1054. // unhighlight anything not here
  1055. if ( hitView != self )
  1056. [self highlightItemAtIndex: NSNotFound animated: NO scrollPosition: AQGridViewScrollPositionNone];
  1057. if ( [self _canSelectItemContainingHitView: hitView] )
  1058. {
  1059. NSUInteger index = [self indexForItemAtPoint: pt];
  1060. if ( index != NSNotFound )
  1061. {
  1062. if ( _flags.allowsSelection == 1 )
  1063. {
  1064. _pendingSelectionIndex = index;
  1065. // NB: In UITableView:
  1066. // if ( [self usesGestureRecognizers] && [self isDragging] ) skip next line
  1067. [self performSelector: @selector(_gridViewDeferredTouchesBegan:)
  1068. withObject: [NSNumber numberWithUnsignedInteger: index]
  1069. afterDelay: 0.0];
  1070. }
  1071. }
  1072. }
  1073. }
  1074. [super touchesBegan: touches withEvent: event];
  1075. }
  1076. /*
  1077. - (void) _cancelContentTouchUsingEvent: (UIEvent *) event forced: (BOOL) forced
  1078. {
  1079. static char * name = "_cancelContentTouchWithEvent:forced:";
  1080. // more manual ObjC runtime calls...
  1081. SEL selector = sel_getUid( name );
  1082. objc_msgSend( self, selector, event, forced );
  1083. }
  1084. */
  1085. - (void) touchesMoved: (NSSet *) touches withEvent: (UIEvent *) event
  1086. {
  1087. if ( _flags.ignoreTouchSelect == 0 )
  1088. {
  1089. Class cls = NSClassFromString(@"UILongPressGestureRecognizer");
  1090. if ( (cls != Nil) && ([cls instancesRespondToSelector: @selector(setNumberOfTouchesRequired:)]) )
  1091. {
  1092. if ( [self _gestureRecognizerIsHandlingTouches: touches] )
  1093. goto passToSuper; // I feel all icky now
  1094. }
  1095. //[self _cancelContentTouchUsingEvent: event forced: NO];
  1096. [self highlightItemAtIndex: NSNotFound animated: NO scrollPosition: AQGridViewScrollPositionNone];
  1097. _flags.ignoreTouchSelect = 1;
  1098. _touchedContentView = nil;
  1099. }
  1100. passToSuper:
  1101. [super touchesMoved: touches withEvent: event];
  1102. }
  1103. - (void) touchesEnded: (NSSet *) touches withEvent: (UIEvent *) event
  1104. {
  1105. [[self class] cancelPreviousPerformRequestsWithTarget: self
  1106. selector: @selector(_gridViewDeferredTouchesBegan:)
  1107. object: nil];
  1108. UIView * hitView = _touchedContentView;
  1109. _touchedContentView = nil;
  1110. [super touchesEnded: touches withEvent: event];
  1111. if ( _touchedContentView != nil )
  1112. {
  1113. hitView = _touchedContentView;
  1114. }
  1115. if ( [hitView superview] == nil )
  1116. {
  1117. hitView = nil;
  1118. }
  1119. // poor-man's goto
  1120. do
  1121. {
  1122. if ( self.dragging )
  1123. break;
  1124. UITouch * touch = [touches anyObject];
  1125. if ( touch == nil )
  1126. break;
  1127. CGPoint pt = [touch locationInView: self];
  1128. if ( (hitView != nil) && ([self _canSelectItemContainingHitView: hitView] == NO) )
  1129. break;
  1130. if ( _pendingSelectionIndex != [self indexForItemAtPoint: pt] )
  1131. break;
  1132. if ( _flags.allowsSelection == 0 )
  1133. break;
  1134. NSSet *touchEventSet = [event allTouches];
  1135. // run this on the next runloop tick
  1136. UserSelectItemIndexParams* selectorParams = [[UserSelectItemIndexParams alloc] init];
  1137. selectorParams.indexNum = _pendingSelectionIndex;
  1138. selectorParams.numFingers = [touchEventSet count];
  1139. [self performSelector: @selector(_userSelectItemAtIndex:)
  1140. withObject: selectorParams
  1141. afterDelay:0.0];
  1142. } while (0);
  1143. if ( _pendingSelectionIndex != NSNotFound )
  1144. [self unhighlightItemAtIndex: _pendingSelectionIndex animated: NO];
  1145. _pendingSelectionIndex = NSNotFound;
  1146. }
  1147. - (void) touchesCancelled: (NSSet *) touches withEvent: (UIEvent *) event
  1148. {
  1149. _pendingSelectionIndex = NSNotFound;
  1150. [self highlightItemAtIndex: NSNotFound animated: NO scrollPosition: AQGridViewScrollPositionNone];
  1151. [super touchesCancelled: touches withEvent: event];
  1152. _touchedContentView = nil;
  1153. }
  1154. - (void)doAddVisibleCell: (UIView *)cell
  1155. {
  1156. [_visibleCells addObject: cell];
  1157. // updated: if we're adding it to our visibleCells collection, really it should be in the gridview.
  1158. if ( cell.superview == nil )
  1159. {
  1160. NSLog( @"Visible cell not in gridview - adding" );
  1161. if ( _backgroundView.superview == self )
  1162. [self insertSubview: cell aboveSubview: _backgroundView];
  1163. else
  1164. [self insertSubview: cell atIndex: 0];
  1165. }
  1166. }
  1167. @end
  1168. #pragma mark -
  1169. @implementation AQGridView (AQCellGridMath)
  1170. - (NSUInteger) visibleCellListIndexForItemIndex: (NSUInteger) itemIndex
  1171. {
  1172. return ( itemIndex - _visibleIndices.location );
  1173. }
  1174. @end
  1175. #pragma mark -
  1176. @implementation AQGridView (AQCellLayout)
  1177. NSArray * __sortDescriptors;
  1178. - (void) sortVisibleCellList
  1179. {
  1180. static dispatch_once_t onceToken;
  1181. dispatch_once(&onceToken, ^{
  1182. __sortDescriptors = [[NSArray alloc] initWithObjects: [[NSSortDescriptor alloc] initWithKey: @"displayIndex" ascending: YES], nil];
  1183. });
  1184. [_visibleCells sortUsingDescriptors: __sortDescriptors];
  1185. }
  1186. - (void) updateGridViewBoundsForNewGridData: (AQGridViewData *) newGridData
  1187. {
  1188. CGPoint oldMaxLocation = CGPointMake(CGRectGetMaxX(self.bounds), CGRectGetMaxY(self.bounds));
  1189. [self updateContentRectWithOldMaxLocation: oldMaxLocation gridSize: [newGridData sizeForEntireGrid]];
  1190. }
  1191. - (void) updateVisibleGridCellsNow
  1192. {
  1193. if ( _reloadingSuspendedCount > 0 )
  1194. return;
  1195. _reloadingSuspendedCount++;
  1196. @autoreleasepool {
  1197. NSIndexSet * newVisibleIndices = [_gridData indicesOfCellsInRect: [self gridViewVisibleBounds]];
  1198. BOOL enableAnim = [UIView areAnimationsEnabled];
  1199. [UIView setAnimationsEnabled: NO];
  1200. @try
  1201. {
  1202. // a couple of simple tests
  1203. // TODO: if we replace _visibleIndices with an index set, this comparison will have to change
  1204. if ( ([_visibleCells count] != [newVisibleIndices count]) ||
  1205. ([newVisibleIndices countOfIndexesInRange: _visibleIndices] != _visibleIndices.length) )
  1206. {
  1207. // something has changed. Compute intersections and remove/add cells as required
  1208. NSIndexSet * currentVisibleIndices = [NSIndexSet indexSetWithIndexesInRange: _visibleIndices];
  1209. // index sets for removed and inserted items
  1210. NSMutableIndexSet * removedIndices = nil, * insertedIndices = nil;
  1211. // handle the simple case first
  1212. // TODO: if we replace _visibleIndices with an index set, this comparison will have to change
  1213. if ( [currentVisibleIndices intersectsIndexesInRange: _visibleIndices] == NO )
  1214. {
  1215. removedIndices = [currentVisibleIndices mutableCopy];
  1216. insertedIndices = [newVisibleIndices mutableCopy];
  1217. }
  1218. else // more complicated -- compute negative intersections
  1219. {
  1220. removedIndices = [[currentVisibleIndices aq_indexesOutsideIndexSet: newVisibleIndices] mutableCopy];
  1221. insertedIndices = [[newVisibleIndices aq_indexesOutsideIndexSet: currentVisibleIndices] mutableCopy];
  1222. }
  1223. if ( [removedIndices count] != 0 )
  1224. {
  1225. NSMutableIndexSet * shifted = [removedIndices mutableCopy];
  1226. // get an index set for everything being removed relative to items' locations within the visible cell list
  1227. [shifted shiftIndexesStartingAtIndex: [removedIndices firstIndex] by: 0 - (NSInteger)_visibleIndices.location];
  1228. //NSLog( @"Removed indices relative to visible cell list: %@", shifted );
  1229. NSUInteger index=[shifted firstIndex];
  1230. while(index != NSNotFound){
  1231. //NSLog(@"%i >= %i ?", index, [_visibleCells count]);
  1232. if (index >= [_visibleCells count]) {
  1233. [shifted removeIndex:index];
  1234. }
  1235. index=[shifted indexGreaterThanIndex: index];
  1236. }
  1237. // pull out the cells for manipulation
  1238. NSMutableArray * removedCells = [[_visibleCells objectsAtIndexes: shifted] mutableCopy];
  1239. // remove them from the visible list
  1240. [_visibleCells removeObjectsInArray: removedCells];
  1241. //NSLog( @"After removals, visible cells count = %lu", (unsigned long)[_visibleCells count] );
  1242. // don't need this any more
  1243. shifted = nil;
  1244. // remove cells from the view hierarchy -- but only if they're not being animated by something else
  1245. NSArray * animating = [[self.animatingCells valueForKey: @"animatingView"] allObjects];
  1246. if ( animating != nil )
  1247. [removedCells removeObjectsInArray: animating];
  1248. // these are not being displayed or animated offscreen-- take them off the screen immediately
  1249. [removedCells makeObjectsPerformSelector: @selector(removeFromSuperview)];
  1250. // put them into the cell reuse queue
  1251. [self enqueueReusableCells: removedCells];
  1252. }
  1253. if ( [insertedIndices count] != 0 )
  1254. {
  1255. // some items are going in -- put them at the end and the sort function will move them to the right index during layout
  1256. // if any of these new indices correspond to animating cells (NOT UIImageViews) then copy them into the visible cell list
  1257. NSMutableIndexSet * animatingInserted = [insertedIndices mutableCopy];
  1258. // compute the intersection of insertedIndices and _animatingIndices
  1259. NSUInteger idx = [insertedIndices firstIndex];
  1260. while ( idx != NSNotFound )
  1261. {
  1262. if ( [_animatingIndices containsIndex: idx] == NO )
  1263. [animatingInserted removeIndex: idx];
  1264. idx = [insertedIndices indexGreaterThanIndex: idx];
  1265. }
  1266. if ( [animatingInserted count] != 0 )
  1267. {
  1268. for ( AQGridViewAnimatorItem * item in _animatingCells )
  1269. {
  1270. if ( [newVisibleIndices containsIndex: item.index] == NO )
  1271. continue;
  1272. if ( [item.animatingView isKindOfClass: [AQGridV