Skip to content
This repository

Add pagingEnabled to the grid and a GMGridViewLayoutHorizontalPagedLtr #1

Closed
wants to merge 8 commits into from

2 participants

Ran Tavory Gulam Moledina
Ran Tavory

Add pagingEnabled to the grid. It simply delegates the property back to the undelying scrollView
Implement a new layout strategy: GMGridViewLayoutHorizontalPagedLtr
And add a few geometry translators that'll translate points and rectangles from the underlying scrollview to the desired view

added some commits November 16, 2011
Ran Tavory Add pagingEnabled to the grid. It simply delegates the property back …
…to the undelying scrollView
b128ad4
Ran Tavory Fix name of isPagingEnabled to pagingEnabled. Old java habits... d58ccf1
Ran Tavory Implement GMGridViewLayoutHorizontalPagedLtr. a layout for paged hori…
…zontal scroll where items in each page flow from top-left to bottom-right
abf5a73
Ran Tavory Add convertScrolledPoint and convertScrolledRect so users of GMGridVi…
…ew are able to get the position of a scrolled element
35b4bda
Ran Tavory Add 'build' to gitignore 53f7a0e
Ran Tavory Add support for better programatic scroll control.
Expose the contentOffset
Allow scrolling to a particular point (and not only to a particular item)
Add more page queries to the GMGridViewLayoutHorizontalPagedLtrStrategy
... it's all part of the same scheme...
92aa779
Ran Tavory Add GMGridViewDidScroll so that users know when GMScrollView is scrol…
…ling
a6d3057
Gulam Moledina
Owner

Looks good ! I'll check it tomorrow and merge it.

Ran Tavory

I added more stuff to this pull request (sorry, I couldn't find a way to create a completely new pull request):

Implement a new layout strategy: GMGridViewLayoutHorizontalPagedLtr

And add a few geometry translators that'll translate points and rectangles from the underlying scrollview to the desired view

Gulam Moledina
Owner

Thanks :)

I looked into this and it's breaking the way the logic is separated right now between the layoutStrategy classes and the main gridView class (for example, the layout isn't supposed to know about any spacing).
Also, it requires users to enable paging themselves and all the other animations when inserting/deleting items don't work for this layout.

I think the paging should be automatically enabled based on the layout being used (a method in the layout class ?) and either keep all the spacing logic in the layout classes or in the gridView.

I've started to think about it, let me know if you have any suggestions !

Ran Tavory
Gulam Moledina
Owner

Scrolling to a particular point will be weird when paging is enabled

OK, this commit has two different things even though in my mind they are related.
We have the two geometry methods:

  • (CGPoint)originForItemAtColumn:(NSInteger)column row:(NSInteger)row page:(NSInteger)page;
  • (CGPoint)originForPage:(NSUInteger)page;

They are useful to know the physical location on the screen a certain item in a page (I use it to 'fly' things away from the grid off to another UIView in an animation when the user touches them)

The second thing is

  • (void)setContentOffset:(CGPoint)offset animated:(BOOL)animated;

Well, I have a paging control that I use to scroll b/w pages. So I use a combination of originForPage and setContentOffset.
I see why this breaks encapsulation and I'm open for suggestions... the goal is to allow an external page control to slide switch pages in the grid.

As a matter of fact when I come to think of it, regarding setContentOffset and pagingEnabled - this is consistent with how UIScrollView behaves. Event in UIScrollView it's possible to programmatically scroll to any point in the scroll view even though the scroll view is paged. So although I agree with you that this is breaking encapsulation to some degree, at least we didn't break it first and we're consistent with apple model ;)

Gulam Moledina
Owner

I agree.
I'll add this on my next update when I'll add an API to allow having a pageControl (so knowing when the page changed).

Gulam Moledina gmoledina closed this December 11, 2011
Gulam Moledina
Owner

Thanks! I have implemented a solution close to what we discussed.

Instead of having the user control the pagingEnabled property of the scrollView, it is set automatically by the gridView depending on the layoutStrategy.

The edgeInsets (minEdgeInsets and centerGrid) logic is moved to the layout classes. The edgeInset is set on each page of the gridView (makes more sense); same applies to centerring the grid".

I just pushed the new code, let me know what you think.

Gulam Moledina
Owner

Is it better to have just one property that controls both horizontal and vertical scroll indicator ?
As we never have both of them visible at the same time anyway.

Alright! I've pulled your code on my local repo. It will be part of the next release.
Thanks!

Ran Tavory
Gulam Moledina
Owner

Support for iOS 4 is 95% implemented :)
Will do some more testing and profiling before I can push the code.

Good luck with your release!

Ran Tavory
ralcazar ralcazar referenced this pull request May 17, 2012
Closed

Problem reusing cells #106

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 8 unique commits by 1 author.

Nov 16, 2011
Ran Tavory Add pagingEnabled to the grid. It simply delegates the property back …
…to the undelying scrollView
b128ad4
Ran Tavory Fix name of isPagingEnabled to pagingEnabled. Old java habits... d58ccf1
Ran Tavory Implement GMGridViewLayoutHorizontalPagedLtr. a layout for paged hori…
…zontal scroll where items in each page flow from top-left to bottom-right
abf5a73
Ran Tavory Add convertScrolledPoint and convertScrolledRect so users of GMGridVi…
…ew are able to get the position of a scrolled element
35b4bda
Ran Tavory Add 'build' to gitignore 53f7a0e
Nov 17, 2011
Ran Tavory Add support for better programatic scroll control.
Expose the contentOffset
Allow scrolling to a particular point (and not only to a particular item)
Add more page queries to the GMGridViewLayoutHorizontalPagedLtrStrategy
... it's all part of the same scheme...
92aa779
Ran Tavory Add GMGridViewDidScroll so that users know when GMScrollView is scrol…
…ling
a6d3057
Dec 07, 2011
Ran Tavory Add support for setting showsVerticalScrollIndicator and showsHorizon…
…talScrollIndicator
bcfa3ff
This page is out of date. Refresh to see the latest.
1  .gitignore
@@ -6,3 +6,4 @@ xcuserdata/
6 6
 *.xcodeproj/xcuserdata/
7 7
 *.xcodeproj/project.xcworkspace/xcuserdata/
8 8
 *.xcuserstate
  9
+build
17  GMGridView/API/GMGridView.h
@@ -71,7 +71,10 @@ typedef enum
71 71
 @property (nonatomic) UIEdgeInsets minEdgeInsets;                     // Default is (5, 5, 5, 5)
72 72
 @property (nonatomic) CFTimeInterval minimumPressDuration;            // Default is 0.2; if set to 0, the scrollView will not be scrollable
73 73
 @property (nonatomic) BOOL showFullSizeViewWithAlphaWhenTransforming; // Default is YES - not working right now
74  
-
  74
+@property (nonatomic) BOOL pagingEnabled;                             // Default is NO
  75
+@property (nonatomic) BOOL showsVerticalScrollIndicator;              // Default is YES
  76
+@property (nonatomic) BOOL showsHorizontalScrollIndicator;            // Default is YES
  77
+@property (nonatomic, readonly) CGPoint contentOffset;                // top-left offset of the visible content (within the internal scroll view)
75 78
 
76 79
 // Reusable cells
77 80
 - (GMGridViewCell *)dequeueReusableCell;
@@ -83,6 +86,14 @@ typedef enum
83 86
 - (void)reloadObjectAtIndex:(NSInteger)index;
84 87
 - (void)swapObjectAtIndex:(NSInteger)index1 withObjectAtIndex:(NSInteger)index2;
85 88
 - (void)scrollToObjectAtIndex:(NSInteger)index;
  89
+- (void)setContentOffset:(CGPoint)offset animated:(BOOL)animated;
  90
+
  91
+// Geometry
  92
+
  93
+// converts a point, taking into account the internal scroll view
  94
+- (CGPoint) convertScrolledPoint:(CGPoint)point toView:(UIView*)view;
  95
+// converts a rect, taking into account the internal scroll position
  96
+- (CGRect) convertScrolledRect:(CGRect)rect toView:(UIView*)view;
86 97
 
87 98
 @end
88 99
 
@@ -114,7 +125,9 @@ typedef enum
114 125
 
115 126
 @required
116 127
 - (void)GMGridView:(GMGridView *)gridView didTapOnItemAtIndex:(NSInteger)position;
117  
-
  128
+@optional
  129
+// tells the delegate that the scroll view just did scroll. similar in concept to [UIScrollView scrollViewDidScroll:]
  130
+- (void)GMGridViewDidScroll:(GMGridView*)gridView;
118 131
 @end
119 132
 
120 133
 
47  GMGridView/API/GMGridView.m
@@ -146,6 +146,7 @@ @implementation GMGridView
146 146
 
147 147
 @synthesize firstPositionLoaded = _firstPositionLoaded;
148 148
 @synthesize lastPositionLoaded = _lastPositionLoaded;
  149
+@synthesize contentOffset;
149 150
 
150 151
 //////////////////////////////////////////////////////////////
151 152
 #pragma mark Constructors and destructor
@@ -318,6 +319,47 @@ - (void)setEditing:(BOOL)editing
318 319
     }
319 320
 }
320 321
 
  322
+- (void)setPagingEnabled:(BOOL)pagingEnabled
  323
+{
  324
+  _scrollView.pagingEnabled = pagingEnabled;
  325
+}
  326
+
  327
+- (BOOL)pagingEnabled
  328
+{
  329
+  return _scrollView.pagingEnabled;
  330
+}
  331
+
  332
+- (CGPoint) contentOffset {
  333
+  return _scrollView.contentOffset;
  334
+}
  335
+
  336
+- (void)setContentOffset:(CGPoint)offset animated:(BOOL)animated {
  337
+  [_scrollView setContentOffset:offset animated:animated];
  338
+}
  339
+
  340
+- (void)setShowsVerticalScrollIndicator:(BOOL)showsVerticalScroll {
  341
+  _scrollView.showsVerticalScrollIndicator = showsVerticalScroll;
  342
+}
  343
+- (BOOL)showsVerticalScrollIndicator {
  344
+  return _scrollView.showsVerticalScrollIndicator;
  345
+}
  346
+- (void)setShowsHorizontalScrollIndicator:(BOOL)showsHorizontalScrollIndicator {
  347
+  _scrollView.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
  348
+}
  349
+- (BOOL)showsHorizontalScrollIndicator {
  350
+  return _scrollView.showsHorizontalScrollIndicator;
  351
+}
  352
+
  353
+//////////////////////////////////////////////////////////////
  354
+// Geometry
  355
+//////////////////////////////////////////////////////////////
  356
+- (CGPoint) convertScrolledPoint:(CGPoint)point toView:(UIView*)view {
  357
+  return [_scrollView convertPoint:point toView:view];
  358
+}
  359
+- (CGRect) convertScrolledRect:(CGRect)rect toView:(UIView*)view {
  360
+  return [_scrollView convertRect:rect toView:view];
  361
+}
  362
+
321 363
 //////////////////////////////////////////////////////////////
322 364
 #pragma mark UIScrollView delegate
323 365
 //////////////////////////////////////////////////////////////
@@ -325,6 +367,9 @@ - (void)setEditing:(BOOL)editing
325 367
 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
326 368
 {
327 369
     [self loadRequiredItems];
  370
+    if ([self.actionDelegate respondsToSelector:@selector(GMGridViewDidScroll:)]) {
  371
+      [self.actionDelegate GMGridViewDidScroll:self];
  372
+    }
328 373
 }
329 374
 
330 375
 //////////////////////////////////////////////////////////////
@@ -962,7 +1007,7 @@ - (void)tapGestureUpdated:(UITapGestureRecognizer *)tapGesture
962 1007
 {
963 1008
     CGPoint locationTouch = [_tapGesture locationInView:_scrollView];
964 1009
     NSInteger position = [self.layoutStrategy itemPositionFromLocation:locationTouch];
965  
-    
  1010
+
966 1011
     if (position != GMGV_INVALID_POSITION) 
967 1012
     {
968 1013
         [self.actionDelegate GMGridView:self didTapOnItemAtIndex:position];
32  GMGridView/API/GMGridViewLayoutStrategies.h
@@ -35,7 +35,20 @@
35 35
 
36 36
 typedef enum {
37 37
     GMGridViewLayoutVertical = 0,
38  
-    GMGridViewLayoutHorizontal
  38
+    GMGridViewLayoutHorizontal,
  39
+    // A paged horizontal strategy in which in each page the items flow from top left to bottom right
  40
+    // Example:
  41
+    // ______________________
  42
+    // | 1  2  3 | 10 11 12 |
  43
+    // | 4  5  6 | 13 14    |
  44
+    // | 7  8  9 |          |
  45
+    // ----------------------
  46
+    // NOTE: For this strategy to work properly and center the elements in each page, make sure that 
  47
+    // the grid's left inset is zero. For example:
  48
+    // gmGridView.minEdgeInsets = UIEdgeInsetsMake(6, 0, 0, 0);
  49
+    // as well as that paging is enabled for this grid:
  50
+    // gmGridView.pagingEnabled = YES;
  51
+    GMGridViewLayoutHorizontalPagedLtr
39 52
 } GMGridViewLayoutStrategyType;
40 53
 
41 54
 
@@ -127,5 +140,22 @@ typedef enum {
127 140
 
128 141
 @end
129 142
 
  143
+//////////////////////////////////////////////////////////////
  144
+#pragma mark - Horizontal Paged LTR strategy
  145
+//////////////////////////////////////////////////////////////
130 146
 
  147
+@interface GMGridViewLayoutHorizontalPagedLtrStrategy : GMGridViewLayoutHorizontalStrategy <GMGridViewLayoutStrategy>
  148
+{
  149
+@protected
  150
+    NSUInteger _numberOfPages;
  151
+    NSUInteger _numberOfColumnsPerPage;
  152
+    // left padding on each page. So that the content can be centered in the page
  153
+    CGFloat _pagePaddingLeft;
  154
+}
131 155
 
  156
+@property (nonatomic, readonly) NSUInteger numberOfPages;
  157
+@property (nonatomic, readonly) NSUInteger numberOfColumnsPerPage;
  158
+- (NSUInteger) pageForContentOffset:(CGPoint)offset;
  159
+- (CGPoint)originForItemAtColumn:(NSInteger)column row:(NSInteger)row page:(NSInteger)page;
  160
+- (CGPoint)originForPage:(NSUInteger)page;
  161
+@end
103  GMGridView/API/GMGridViewLayoutStrategies.m
@@ -43,6 +43,9 @@ @implementation GMGridViewLayoutStrategyFactory
43 43
         case GMGridViewLayoutVertical:
44 44
             strategy = [[GMGridViewLayoutVerticalStrategy alloc] init];
45 45
             break;
  46
+        case GMGridViewLayoutHorizontalPagedLtr:
  47
+            strategy = [[GMGridViewLayoutHorizontalPagedLtrStrategy alloc] init];
  48
+            break;
46 49
         case GMGridViewLayoutHorizontal:
47 50
         default:
48 51
             strategy = [[GMGridViewLayoutHorizontalStrategy alloc] init];
@@ -280,11 +283,111 @@ - (NSRange)rangeOfPositionsInBoundsFromOffset:(CGPoint)offset
280 283
 @end
281 284
 
282 285
 
  286
+//////////////////////////////////////////////////////////////
  287
+#pragma mark - 
  288
+#pragma mark - Horizontal Paged LTR strategy implementation
  289
+//////////////////////////////////////////////////////////////
283 290
 
  291
+@implementation GMGridViewLayoutHorizontalPagedLtrStrategy
284 292
 
  293
+@synthesize numberOfPages = _numberOfPages;
  294
+@synthesize numberOfColumnsPerPage = _numberOfColumnsPerPage;
285 295
 
  296
+- (id)init
  297
+{
  298
+  if ((self = [super init])) 
  299
+  {
  300
+    _type = GMGridViewLayoutHorizontalPagedLtr;
  301
+  }
  302
+  return self;
  303
+}
  304
+
  305
+- (void)rebaseWithItemCount:(NSInteger)count havingSize:(CGSize)itemSize andSpacing:(NSInteger)spacing insideOfBounds:(CGRect)bounds
  306
+{
  307
+  [super rebaseWithItemCount:count havingSize:itemSize andSpacing:spacing insideOfBounds:bounds];
  308
+  _numberOfPages = ceil(self.contentSize.width / bounds.size.width);
  309
+  _contentSize.width = _numberOfPages * bounds.size.width;
  310
+  _numberOfColumnsPerPage = floor(bounds.size.width / (itemSize.width + spacing));
  311
+  
  312
+  // The total size of each page's 'content' area from which the left padding will be calculated so that the content can be centered in the page
  313
+  NSUInteger pageContentwidth = _numberOfColumnsPerPage * (itemSize.width + spacing) - spacing; 
  314
+  _pagePaddingLeft = (bounds.size.width - pageContentwidth) / 2;
  315
+}
  316
+
  317
+- (CGPoint)originForItemAtColumn:(NSInteger)column row:(NSInteger)row page:(NSInteger)page 
  318
+{
  319
+  CGFloat x = page * self.contentBounds.size.width + _pagePaddingLeft + column * (self.itemSize.width + self.itemSpacing);
  320
+  CGFloat y = row * (self.itemSize.height + self.itemSpacing);
  321
+  return CGPointMake(x, y);
  322
+}
  323
+
  324
+- (CGPoint)originForItemAtPosition:(NSInteger)position
  325
+{
  326
+  // page, column and row are zero based
  327
+  NSUInteger page = floor(position / (self.numberOfColumnsPerPage * self.numberOfItemsPerColumn));
  328
+  NSUInteger column = position % self.numberOfColumnsPerPage; // The columns is a column within the page
  329
+  NSUInteger row = (NSUInteger) floor(position / self.numberOfColumnsPerPage) % self.numberOfItemsPerColumn;
  330
+  return [self originForItemAtColumn:column row:row page:page];
  331
+}
286 332
 
  333
+- (NSInteger)itemPositionFromLocation:(CGPoint)location
  334
+{
  335
+  NSUInteger page = [self pageForContentOffset:location];
  336
+  NSUInteger column = (location.x - page * self.contentBounds.size.width - _pagePaddingLeft) / (self.itemSize.width + self.itemSpacing);
  337
+  NSUInteger row = (int) (location.y / (self.itemSize.height + self.itemSpacing));
  338
+  
  339
+  NSInteger position = page * self.numberOfItemsPerColumn * self.numberOfColumnsPerPage + row * self.numberOfColumnsPerPage + column;
  340
+  
  341
+  if (position >= [self itemCount] || position < 0) 
  342
+  {
  343
+    position = GMGV_INVALID_POSITION;
  344
+  }
  345
+  else
  346
+  {
  347
+    CGPoint itemOrigin = [self originForItemAtPosition:position];
  348
+    CGRect itemFrame = CGRectMake(itemOrigin.x, 
  349
+                                  itemOrigin.y, 
  350
+                                  self.itemSize.width, 
  351
+                                  self.itemSize.height);
  352
+    
  353
+    if (!CGRectContainsPoint(itemFrame, location)) 
  354
+    {
  355
+      position = GMGV_INVALID_POSITION;
  356
+    }
  357
+  }
  358
+  
  359
+  return position;
  360
+}
287 361
 
  362
+- (NSRange) rangeOfPositionsFromPage:(NSUInteger)page
  363
+{
  364
+  NSUInteger itemsPerPage = self.numberOfItemsPerColumn * self.numberOfColumnsPerPage;
  365
+  // in theory it's correct to return the following:
  366
+  //return NSMakeRange(page * itemsPerPage, itemsPerPage);
  367
+  // however, if we do that, scrolling would not work smmothly and users would notice the cells being loaded
  368
+  // with each page transition.
  369
+  // Therefore instead, we return a larger range which includes two more pages, one to the left of this page and another to the right
  370
+  // It comes on the expense of memory, but provides smoother experience
  371
+  NSUInteger pageToTheLeft = page == 0 ? 0 : page - 1;
  372
+  NSUInteger pageToTheRight = page == self.numberOfPages ? page : page + 1;
  373
+  return NSMakeRange(pageToTheLeft * itemsPerPage, (pageToTheRight - pageToTheLeft + 1) * itemsPerPage);
  374
+}
288 375
 
  376
+- (NSRange)rangeOfPositionsInBoundsFromOffset:(CGPoint)offset
  377
+{
  378
+  CGPoint contentOffset = CGPointMake(MAX(0, offset.x), 
  379
+                                      MAX(0, offset.y));
  380
+  NSUInteger page = floor(contentOffset.x / _contentBounds.size.width);
  381
+  return [self rangeOfPositionsFromPage:page];
  382
+}
289 383
 
  384
+- (NSUInteger) pageForContentOffset:(CGPoint)offset
  385
+{
  386
+  return floor(offset.x / self.contentBounds.size.width);
  387
+}
  388
+- (CGPoint)originForPage:(NSUInteger)page 
  389
+{
  390
+  return CGPointMake(page * self.contentBounds.size.width, 0);
  391
+}
290 392
 
  393
+@end
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.