Skip to content

Commit

Permalink
Cleanup ATArrayView & ATPagingView to better match UITableView API (a…
Browse files Browse the repository at this point in the history
…nd each other's impls)
  • Loading branch information
andreyvit committed Jan 31, 2011
1 parent 86fb537 commit 14ab148
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 100 deletions.
2 changes: 1 addition & 1 deletion ATArrayView/ATArrayView.h
Expand Up @@ -46,7 +46,7 @@

@property(nonatomic, readonly) NSInteger lastVisibleItemIndex;

- (void)reloadItems; // must be called at least once to display something
- (void)reloadData; // must be called at least once to display something

- (UIView *)viewForItemAtIndex:(NSUInteger)index; // nil if not loaded

Expand Down
30 changes: 15 additions & 15 deletions ATArrayView/ATArrayView.m
Expand Up @@ -7,7 +7,7 @@

@interface ATArrayView () <UIScrollViewDelegate>

- (void)updateItemViews:(BOOL)updateExisting;
- (void)configureItems:(BOOL)updateExisting;
- (void)configureItem:(UIView *)item forIndex:(NSInteger)index;
- (void)recycleItem:(UIView *)item;

Expand Down Expand Up @@ -52,9 +52,9 @@ - (void)dealloc {


#pragma mark -
#pragma mark Data Changes
#pragma mark Data

- (void)reloadItems {
- (void)reloadData {
_itemCount = [_delegate numberOfItemsInArrayView:self];

// recycle all items
Expand All @@ -63,12 +63,12 @@ - (void)reloadItems {
}
[_visibleItems removeAllObjects];

[self updateItemViews:NO];
[self configureItems:NO];
}


#pragma mark -
#pragma mark Item View Management
#pragma mark Item Views

- (UIView *)viewForItemAtIndex:(NSUInteger)index {
for (UIView *item in _visibleItems)
Expand All @@ -77,13 +77,7 @@ - (UIView *)viewForItemAtIndex:(NSUInteger)index {
return nil;
}

- (void)configureItem:(UIView *)item forIndex:(NSInteger)index {
item.tag = index;
item.frame = [self rectForItemAtIndex:index];
[item setNeedsDisplay]; // just in case
}

- (void)updateItemViews:(BOOL)reconfigure {
- (void)configureItems:(BOOL)reconfigure {
// update content size if needed
CGSize contentSize = CGSizeMake(self.bounds.size.width,
_itemSize.height * _rowCount + _rowGap * (_rowCount - 1) + _effectiveInsets.top + _effectiveInsets.bottom);
Expand Down Expand Up @@ -117,6 +111,12 @@ - (void)updateItemViews:(BOOL)reconfigure {
}
}

- (void)configureItem:(UIView *)item forIndex:(NSInteger)index {
item.tag = index;
item.frame = [self rectForItemAtIndex:index];
[item setNeedsDisplay]; // just in case
}


#pragma mark -
#pragma mark Layouting
Expand Down Expand Up @@ -146,7 +146,7 @@ - (void)layoutSubviews {
_contentInsets.bottom + _rowGap,
_contentInsets.right + _colGap);

[self updateItemViews:boundsChanged];
[self configureItems:boundsChanged];
}

- (NSInteger)firstVisibleItemIndex {
Expand Down Expand Up @@ -192,7 +192,7 @@ - (UIView *)dequeueReusableItem {
#pragma mark UIScrollViewDelegate methods

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[self updateItemViews:NO];
[self configureItems:NO];
}

@end
Expand Down Expand Up @@ -230,7 +230,7 @@ - (void)viewDidLoad {

- (void)viewWillAppear:(BOOL)animated {
if (self.arrayView.itemCount == 0)
[self.arrayView reloadItems];
[self.arrayView reloadData];
}


Expand Down
5 changes: 4 additions & 1 deletion ATPagingView/ATPagingView.h
Expand Up @@ -34,7 +34,10 @@

@property(nonatomic, assign, readonly) NSInteger currentPageIndex;

- (void)reloadPages; // must be called at least once to display something
@property(nonatomic, assign, readonly) NSInteger firstVisiblePageIndex;
@property(nonatomic, assign, readonly) NSInteger lastVisiblePageIndex;

- (void)reloadData; // must be called at least once to display something

- (UIView *)viewForPageAtIndex:(NSUInteger)index; // nil if not loaded

Expand Down
168 changes: 88 additions & 80 deletions ATPagingView/ATPagingView.m
Expand Up @@ -7,14 +7,14 @@

@interface ATPagingView () <UIScrollViewDelegate>

- (void)layoutPages;
- (void)recycleAllPages;
- (void)configureScrollView;
- (void)reconfigurePageAtIndex:(NSInteger)index;
- (void)configurePages;
- (void)configurePage:(UIView *)page forIndex:(NSInteger)index;

- (CGRect)frameForScrollView;
- (CGRect)frameForPageAtIndex:(NSUInteger)index;

- (void)recyclePage:(UIView *)page;

@end


Expand Down Expand Up @@ -68,12 +68,29 @@ - (void)setGapBetweenPages:(CGFloat)value {

- (void)setPagesToPreload:(NSInteger)value {
_pagesToPreload = value;
[self layoutPages];
[self configurePages];
}


#pragma mark -
#pragma mark Accessing and Managing Pages
#pragma mark Data

- (void)reloadData {
_pageCount = [_delegate numberOfPagesInPagingView:self];

// recycle all pages
for (UIView *view in _visiblePages) {
[_recycledPages addObject:view];
[view removeFromSuperview];
}
[_visiblePages removeAllObjects];

[self configurePages];
}


#pragma mark -
#pragma mark Page Views

- (UIView *)viewForPageAtIndex:(NSUInteger)index {
for (UIView *page in _visiblePages)
Expand All @@ -82,11 +99,55 @@ - (UIView *)viewForPageAtIndex:(NSUInteger)index {
return nil;
}

- (void)reloadPages {
_pageCount = [_delegate numberOfPagesInPagingView:self];
[self configureScrollView];
[self recycleAllPages];
[self layoutPages];
- (void)configurePages {
if (_scrollView.frame.size.width == 0)
return; // not our time yet

// normally layoutSubviews won't even call us, but protect against any other calls too (e.g. if someones does reloadPages)
if (_rotationInProgress)
return;

CGSize contentSize = CGSizeMake(_scrollView.frame.size.width * _pageCount, _scrollView.frame.size.height);
if (!CGSizeEqualToSize(_scrollView.contentSize, contentSize)) {
_scrollView.contentSize = contentSize;
}

// calculate which pages are visible
int firstPage = self.firstVisiblePageIndex;
int lastPage = self.lastVisiblePageIndex;

// recycle no longer visible pages
for (UIView *page in _visiblePages) {
if (page.tag < firstPage || page.tag > lastPage) {
[self recyclePage:page];
}
}
[_visiblePages minusSet:_recycledPages];

// add missing pages
for (int index = firstPage; index <= lastPage; index++) {
if ([self viewForPageAtIndex:index] == nil) {
UIView *page = [_delegate viewForPageInPagingView:self atIndex:index];
[self configurePage:page forIndex:index];
[_scrollView addSubview:page];
[_visiblePages addObject:page];
}
}

CGRect visibleBounds = _scrollView.bounds;
NSInteger newPageIndex = MIN(MAX(floorf(CGRectGetMidX(visibleBounds) / CGRectGetWidth(visibleBounds)), 0), _pageCount - 1);
if (newPageIndex != _currentPageIndex) {
_currentPageIndex = newPageIndex;
if ([_delegate respondsToSelector:@selector(currentPageDidChangeInPagingView:)])
[_delegate currentPageDidChangeInPagingView:self];
NSLog(@"_currentPageIndex == %d", _currentPageIndex);
}
}

- (void)configurePage:(UIView *)page forIndex:(NSInteger)index {
page.tag = index;
page.frame = [self frameForPageAtIndex:index];
[page setNeedsDisplay]; // just in case
}


Expand All @@ -99,8 +160,7 @@ - (void)willAnimateRotation {
// recycle non-current pages, otherwise they might show up during the rotation
for (UIView *view in _visiblePages)
if (view.tag != _currentPageIndex) {
[_recycledPages addObject:view];
[view removeFromSuperview];
[self recyclePage:view];
}
[_visiblePages minusSet:_recycledPages];

Expand All @@ -119,9 +179,6 @@ - (void)willAnimateRotation {
}

- (void)didRotate {
// update contentSize
[self configureScrollView];

// adjust frames according to the new page size - this does not cause any visible changes,
// because we move the pages and adjust contentOffset simultaneously
for (UIView *view in _visiblePages)
Expand All @@ -130,7 +187,7 @@ - (void)didRotate {

_rotationInProgress = NO;

[self layoutPages];
[self configurePages];
}


Expand All @@ -154,74 +211,26 @@ - (void)layoutSubviews {
} else if (oldFrame.size.height != _scrollView.frame.size.height) {
// some other height change (the initial change from 0 to some specific size,
// or maybe an in-call status bar has appeared or disappeared)
[self configureScrollView];
[self layoutPages];
[self configurePages];
}
}

- (void)layoutPages {
// normally layoutSubviews won't even call us, but protect against any other calls too (e.g. if someones calls reloadPages)
if (_rotationInProgress)
return;

// calculate which pages are visible
- (NSInteger)firstVisiblePageIndex {
CGRect visibleBounds = _scrollView.bounds;
int firstNeededPageIndex = floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds));
int lastNeededPageIndex = floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds));
firstNeededPageIndex = MAX(firstNeededPageIndex, 0);
lastNeededPageIndex = MIN(lastNeededPageIndex, _pageCount - 1);

// recycle no longer visible pages
for (UIView *page in _visiblePages) {
if (page.tag < firstNeededPageIndex || page.tag > lastNeededPageIndex) {
[_recycledPages addObject:page];
[page removeFromSuperview];
}
}
[_visiblePages minusSet:_recycledPages];

// add missing pages
for (int index = firstNeededPageIndex; index <= lastNeededPageIndex; index++) {
if ([self viewForPageAtIndex:index] == nil) {
UIView *page = [_delegate viewForPageInPagingView:self atIndex:index];
[self configurePage:page forIndex:index];
[_scrollView addSubview:page];
[_visiblePages addObject:page];
}
}

NSInteger newPageIndex = MIN(MAX(floorf(CGRectGetMidX(visibleBounds) / CGRectGetWidth(visibleBounds)), 0), _pageCount - 1);
if (newPageIndex != _currentPageIndex) {
_currentPageIndex = newPageIndex;
if ([_delegate respondsToSelector:@selector(currentPageDidChangeInPagingView:)])
[_delegate currentPageDidChangeInPagingView:self];
NSLog(@"_currentPageIndex == %d", _currentPageIndex);
}
}

// should be called when the number of rows or the scroll view frame changes
- (void)configureScrollView {
if (_scrollView.frame.size.width == 0)
return; // not our time yet
_scrollView.contentSize = CGSizeMake(_scrollView.frame.size.width * _pageCount, _scrollView.frame.size.height);
//_scrollView.contentOffset = CGPointZero;
return MAX(floorf(CGRectGetMinX(visibleBounds) / CGRectGetWidth(visibleBounds)), 0);
}

- (void)reconfigurePageAtIndex:(NSInteger)index {
[self configurePage:[self viewForPageAtIndex:index] forIndex:index];
}

- (void)configurePage:(UIView *)page forIndex:(NSInteger)index {
page.tag = index;
page.frame = [self frameForPageAtIndex:index];
[page setNeedsDisplay]; // just in case
- (NSInteger)lastVisiblePageIndex {
CGRect visibleBounds = _scrollView.bounds;
return MIN(floorf((CGRectGetMaxX(visibleBounds)-1) / CGRectGetWidth(visibleBounds)), _pageCount - 1);
}

- (CGRect)frameForScrollView {
CGSize size = self.bounds.size;
return CGRectMake(-_gapBetweenPages/2, 0, size.width + _gapBetweenPages, size.height);
}

// not public because this is in scroll view coordinates
- (CGRect)frameForPageAtIndex:(NSUInteger)index {
CGFloat pageWidthWithGap = _scrollView.frame.size.width;
CGSize pageSize = self.bounds.size;
Expand All @@ -234,12 +243,11 @@ - (CGRect)frameForPageAtIndex:(NSUInteger)index {
#pragma mark -
#pragma mark Recycling

- (void)recycleAllPages {
for (UIView *view in _visiblePages) {
[_recycledPages addObject:view];
[view removeFromSuperview];
}
[_visiblePages removeAllObjects];
// It's the caller's responsibility to remove this page from _visiblePages,
// since this method is often called while traversing _visiblePages array.
- (void)recyclePage:(UIView *)page {
[_recycledPages addObject:page];
[page removeFromSuperview];
}

- (UIView *)dequeueReusablePage {
Expand All @@ -257,7 +265,7 @@ - (UIView *)dequeueReusablePage {
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (_rotationInProgress)
return;
[self layoutPages];
[self configurePages];
}

@end
Expand Down Expand Up @@ -295,7 +303,7 @@ - (void)viewDidLoad {

- (void)viewWillAppear:(BOOL)animated {
if (self.pagingView.pageCount == 0)
[self.pagingView reloadPages];
[self.pagingView reloadData];
}


Expand Down
6 changes: 3 additions & 3 deletions README.md
Expand Up @@ -39,14 +39,14 @@ ATPagingViewController is similar to UITableViewController and:

* defines `loadView` to create ATPagingView automatically,
* sets itself as a delegate of ATPagingView,
* calls `reloadPages` in `viewWillAppear:` if the paging view is empty,
* calls `reloadData` in `viewWillAppear:` if the paging view is empty,
* additionally it forwards orientation events to the paging view (see below).

If you want to use ATPagingView without ATPagingViewController, you
need to:

* provide your delegate object using the `delegate` property,
* call `reloadPages` to populate the view,
* call `reloadData` to populate the view,
* if you want to support rotation, you need to invoke
`willAnimateRotation` and `didRotate` methods from your view
controller:
Expand Down Expand Up @@ -86,4 +86,4 @@ it:

* overrides `loadView` to create ATArrayView automatically,
* sets itself as a delegate of the array view,
* calls `reloadItems` in `viewWillAppear:` if the array view is empty.
* calls `reloadData` in `viewWillAppear:` if the array view is empty.

0 comments on commit 14ab148

Please sign in to comment.