Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Integrate the pull to refresh into RCLTVC

  • Loading branch information...
commit 89112b23c46957e868087c985920b38f46f70b4e 1 parent 204a066
@clawoo authored
View
32 Classes/RCLTableRefreshHeader.h
@@ -0,0 +1,32 @@
+//
+// RCLTableRefreshHeader.h
+// RCL
+//
+// Created by Clawoo on 10/3/11.
+// Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+#define kRCLRefreshHeaderHeight 52.0f
+
+typedef enum {
+ RCLTableRefreshStateNormal,
+ RCLTableRefreshStateDragging,
+ RCLTableRefreshStateLoading
+} RCLTableRefreshState;
+
+@interface RCLTableRefreshHeader : UIView {
+ UILabel *refreshLabel_;
+ UILabel *lastRefreshLabel_;
+ UIImageView *refreshArrow_;
+ UIActivityIndicatorView *refreshSpinner_;
+
+ RCLTableRefreshState state_;
+}
+
+- (void)setLightStyle:(BOOL)lightStyle;
+- (void)setState:(RCLTableRefreshState)state;
+- (void)setLastRefreshDate:(NSDate *)lastRefreshDate;
+
+@end
View
112 Classes/RCLTableRefreshHeader.m
@@ -0,0 +1,112 @@
+//
+// RCLTableRefreshHeader.m
+// RCL
+//
+// Created by Clawoo on 10/3/11.
+// Copyright 2011 __MyCompanyName__. All rights reserved.
+//
+
+#import "RCLTableRefreshHeader.h"
+
+@implementation RCLTableRefreshHeader
+
+- (id)init
+{
+ self = [self initWithFrame:CGRectMake(0, 0 - kRCLRefreshHeaderHeight, 320, kRCLRefreshHeaderHeight)];
+ if (self) {
+
+ }
+ return self;
+}
+
+- (id)initWithFrame:(CGRect)frame
+{
+ self = [super initWithFrame:frame];
+ if (self) {
+ self.backgroundColor = [UIColor clearColor];
+
+ refreshLabel_ = [[UILabel alloc] initWithFrame:CGRectMake(0, 10, 320, 20)];
+ refreshLabel_.backgroundColor = [UIColor clearColor];
+ refreshLabel_.font = [UIFont boldSystemFontOfSize:12.0];
+ refreshLabel_.textAlignment = UITextAlignmentCenter;
+
+ lastRefreshLabel_ = [[UILabel alloc] initWithFrame:CGRectMake(0, kRCLRefreshHeaderHeight - 23, 320, 20)];
+ lastRefreshLabel_.backgroundColor = [UIColor clearColor];
+ lastRefreshLabel_.font = [UIFont systemFontOfSize:12.0];
+ lastRefreshLabel_.textAlignment = UITextAlignmentCenter;
+
+ refreshArrow_ = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"RCLResources.bundle/arrow.png"]];
+ refreshArrow_.frame = CGRectMake((kRCLRefreshHeaderHeight - 27) / 2,
+ (kRCLRefreshHeaderHeight - 44) / 2,
+ 27, 44);
+
+ refreshSpinner_ = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
+ refreshSpinner_.frame = CGRectMake((kRCLRefreshHeaderHeight - 20) / 2, (kRCLRefreshHeaderHeight - 20) / 2, 20, 20);
+ refreshSpinner_.hidesWhenStopped = YES;
+
+ [self addSubview:refreshLabel_];
+ [self addSubview:lastRefreshLabel_];
+ [self addSubview:refreshArrow_];
+ [self addSubview:refreshSpinner_];
+ }
+ return self;
+}
+
+- (void)setLightStyle:(BOOL)lightStyle {
+ if (lightStyle) {
+ refreshLabel_.textColor = [UIColor lightTextColor];
+ lastRefreshLabel_.textColor = [UIColor lightTextColor];
+ refreshArrow_.image = [UIImage imageNamed:@"RCLResources.bundle/arrow-light.png"];
+ refreshSpinner_.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite;
+ }
+ else {
+
+ refreshLabel_.textColor = [UIColor darkTextColor];
+ lastRefreshLabel_.textColor = [UIColor darkTextColor];
+ refreshArrow_.image = [UIImage imageNamed:@"RCLResources.bundle/arrow.png"];
+ refreshSpinner_.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
+ }
+}
+
+- (void)setState:(RCLTableRefreshState)state {
+ state_ = state;
+ // Update the arrow direction and label
+ [UIView beginAnimations:nil context:NULL];
+ switch (state) {
+ case RCLTableRefreshStateNormal:
+ refreshLabel_.text = @"Pull down to refresh...";
+ refreshArrow_.hidden = NO;
+ refreshArrow_.transform = CGAffineTransformMakeRotation(M_PI * 2);
+ [refreshSpinner_ stopAnimating];
+ break;
+ case RCLTableRefreshStateDragging:
+ refreshLabel_.text = @"Release to refresh...";
+ refreshArrow_.transform = CGAffineTransformMakeRotation(M_PI);
+ break;
+ case RCLTableRefreshStateLoading:
+ [UIView setAnimationDuration:0.3];
+ refreshLabel_.text = @"Loading...";
+ refreshArrow_.hidden = YES;
+ [refreshSpinner_ startAnimating];
+ break;
+
+ default:
+ break;
+ }
+ [UIView commitAnimations];
+}
+
+- (void)setLastRefreshDate:(NSDate *)lastRefreshDate {
+ NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
+ [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
+ [dateFormatter setTimeStyle:NSDateFormatterMediumStyle];
+
+ lastRefreshLabel_.text = [NSString stringWithFormat:@"Last updated: %@",[dateFormatter stringFromDate:lastRefreshDate]];
+}
+
+- (void)dealloc
+{
+ [super dealloc];
+}
+
+@end
View
58 Classes/RCLTableViewController.h
@@ -7,6 +7,7 @@
//
#import <UIKit/UIKit.h>
+#import "RCLTableRefreshHeader.h"
#define kRCLTableViewResultsPerPage 10
@@ -15,36 +16,61 @@
with pagination and other functionality
*/
@interface RCLTableViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> {
- NSMutableArray *dataSource_;
- NSInteger totalResults_;
- NSInteger resultsPerPage_;
- BOOL isLoadingNextPage_;
- BOOL morePagesAreAvailable_;
- NSDate *lastRefreshDate_;
- BOOL reloadDataWhenStoppedDecelerating_;
- UITableView *tableView_;
- /*!
- The 'loading more results' loading view
- */
- UIView *loadingView_;
+
+ BOOL isPaginationEnabled_;
+ BOOL isPullToRefreshEnabled_;
+
+ UITableView *tableView_;
+ NSMutableArray *dataSource_;
+ UIView *loadingView_;
+
+ NSInteger totalResults_;
+ NSInteger resultsPerPage_;
+ BOOL isLoadingNextPage_;
+ BOOL morePagesAreAvailable_;
+ BOOL reloadDataWhenStoppedDecelerating_;
+
+ // pull to refresh
+ RCLTableRefreshHeader *refreshHeaderView_;
+ NSDate *lastRefreshDate_;
+ BOOL isDragging_;
+ BOOL isReloading_;
+
}
@property (nonatomic, retain) NSMutableArray *dataSource;
-@property (nonatomic, retain) NSDate *lastRefreshDate;
@property (nonatomic, retain) IBOutlet UITableView *tableView;
-
+@property (nonatomic, retain) NSDate *lastRefreshDate;
+@property (nonatomic, assign) BOOL isPaginationEnabled;
+@property (nonatomic, assign) BOOL isPullToRefreshEnabled;
/*!
- This method is called when a new page of data is needed.
+ This method is called when a new page of data is needed. Your implementation
+ should override it and call super.
@param resultsCount The number of results requested (usually kRCLTableViewResultsPerPage)
@param fromIndex The index from where to load results from.
*/
- (void)startLoadingResults:(NSInteger)resultsCount fromIndex:(NSInteger)fromIndex;
/*!
- This method should be manually called when results have been loaded.
+ This method should be manually called when results have been loaded. Your implementation
+ should call it after the results have been loaded.
@param resultsCount The number of results that have actually been added to the dataSource.
@param fromIndex The index the results start from.
*/
- (void)didEndLoadingResults:(NSInteger)resultsCount fromIndex:(NSInteger)fromIndex;
+
+/*!
+ This method is called when the user has completed a PTR gesture.
+ The table view controller should override this method and start loading data
+ in background
+ */
+- (void)startReloadingResults;
+
+/*!
+ This method must be called manually after the reload of the data has been
+ completed.
+ */
+- (void)didEndReloadingResults;
+
@end
View
204 Classes/RCLTableViewController.m
@@ -8,10 +8,16 @@
#import "RCLTableViewController.h"
+@interface RCLTableViewController (Private)
+- (void)addRefreshHeaderView;
+@end
+
@implementation RCLTableViewController
@synthesize dataSource = dataSource_;
@synthesize lastRefreshDate = lastRefreshDate_;
@synthesize tableView = tableView_;
+@synthesize isPullToRefreshEnabled = isPullToRefreshEnabled_;
+@synthesize isPaginationEnabled = isPaginationEnabled_;
- (void)viewDidLoad {
[super viewDidLoad];
@@ -26,36 +32,70 @@ - (void)viewDidLoad {
[self.view addSubview:tableView_];
}
- if (loadingView_ == nil) {
- loadingView_ = [[UIView alloc] initWithFrame:CGRectMake(0, self.tableView.frame.origin.y + self.tableView.frame.size.height - 18, self.tableView.frame.size.width, 18)];
- loadingView_.backgroundColor = [UIColor colorWithRed:.3 green:.3 blue:.3 alpha:.8];
- loadingView_.alpha = 1;
-
- UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, loadingView_.frame.size.width, loadingView_.frame.size.height)] autorelease];
- label.textAlignment = UITextAlignmentCenter;
- label.textColor = [UIColor whiteColor];
- label.backgroundColor = [UIColor clearColor];
- label.font = [UIFont systemFontOfSize:13];
- label.text = @"Loading results...";
-
- UIActivityIndicatorView *paginationSpinner = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] autorelease];
- paginationSpinner.tag = 1;
-
- CGRect frame = paginationSpinner.frame;
- frame.size = CGSizeMake(17, 17);
- frame.origin.x = (loadingView_.frame.size.width - [label.text sizeWithFont:label.font].width)/2 - frame.size.width - 2;
- frame.origin.y = (loadingView_.frame.size.height - frame.size.height)/2;
- paginationSpinner.frame = frame;
-
- [loadingView_ addSubview:label];
- [loadingView_ addSubview:paginationSpinner];
+ [self setIsPaginationEnabled:isPaginationEnabled_];
+ [self setIsPullToRefreshEnabled:isPullToRefreshEnabled_];
+}
+
+- (void)addRefreshHeaderView {
+ refreshHeaderView_ = [[RCLTableRefreshHeader alloc] init];
+ [self.tableView addSubview:refreshHeaderView_];
+ [refreshHeaderView_ release];
+}
+
+- (void)setIsPaginationEnabled:(BOOL)flag
+{
+ isPaginationEnabled_ = flag;
+ if (isPaginationEnabled_) {
+ if (loadingView_ == nil) {
+ loadingView_ = [[UIView alloc] initWithFrame:CGRectMake(0, self.tableView.frame.origin.y + self.tableView.frame.size.height - 18, self.tableView.frame.size.width, 18)];
+ loadingView_.backgroundColor = [UIColor colorWithRed:.3 green:.3 blue:.3 alpha:.8];
+ loadingView_.alpha = 1;
+
+ UILabel *label = [[[UILabel alloc] initWithFrame:CGRectMake(0, 0, loadingView_.frame.size.width, loadingView_.frame.size.height)] autorelease];
+ label.textAlignment = UITextAlignmentCenter;
+ label.textColor = [UIColor whiteColor];
+ label.backgroundColor = [UIColor clearColor];
+ label.font = [UIFont systemFontOfSize:13];
+ label.text = @"Loading results...";
+
+ UIActivityIndicatorView *paginationSpinner = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite] autorelease];
+ paginationSpinner.tag = 1;
+
+ CGRect frame = paginationSpinner.frame;
+ frame.size = CGSizeMake(17, 17);
+ frame.origin.x = (loadingView_.frame.size.width - [label.text sizeWithFont:label.font].width)/2 - frame.size.width - 2;
+ frame.origin.y = (loadingView_.frame.size.height - frame.size.height)/2;
+ paginationSpinner.frame = frame;
+
+ [loadingView_ addSubview:label];
+ [loadingView_ addSubview:paginationSpinner];
+ }
+ }
+}
+
+- (void)setIsPullToRefreshEnabled:(BOOL)flag
+{
+ isPullToRefreshEnabled_ = flag;
+ if (isPullToRefreshEnabled_) {
+ if (![refreshHeaderView_ superview]) {
+ [self addRefreshHeaderView];
+ }
+ }
+ else {
+ [refreshHeaderView_ removeFromSuperview];
+ refreshHeaderView_ = nil;
}
}
+
+#pragma mark -
+#pragma mark Pagination methods
- (void)startLoadingResults:(NSInteger)resultsCount fromIndex:(NSInteger)fromIndex {
+
if (isLoadingNextPage_) {
return;
}
+
isLoadingNextPage_ = YES;
if (self.tableView.superview) {
CGPoint positionInWindow = [self.tableView.superview convertPoint:self.tableView.frame.origin toView:self.tableView.superview];
@@ -82,29 +122,100 @@ - (void)didEndLoadingResults:(NSInteger)resultsCount fromIndex:(NSInteger)fromIn
[self.tableView reloadData];
}
+#pragma mark -
+#pragma mark Pull to refresh methods
+- (void)startReloadingResults {
+ isReloading_ = YES;
+
+ // Show the header
+ [refreshHeaderView_ setState:RCLTableRefreshStateLoading];
+}
+
+- (void)didEndReloadingResults {
+ isReloading_ = NO;
+
+ // Hide the header
+ [UIView beginAnimations:nil context:NULL];
+ [UIView setAnimationDelegate:self];
+ [UIView setAnimationDuration:0.3];
+ [UIView setAnimationDidStopSelector:@selector(stopLoadingComplete:finished:context:)];
+ self.tableView.contentInset = UIEdgeInsetsZero;
+ [UIView commitAnimations];
+ self.lastRefreshDate = [NSDate date];
+ [refreshHeaderView_ setLastRefreshDate:lastRefreshDate_];
+}
+
+- (void)stopLoadingComplete:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
+ // Reset the header
+ [refreshHeaderView_ setState:RCLTableRefreshStateNormal];
+}
+
+#pragma mark -
+#pragma mark UIScrollView methods
+- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
+
+ if (isPullToRefreshEnabled_) {
+ if (isReloading_) return;
+ isDragging_ = YES;
+ }
+}
+
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
- if (![dataSource_ count]) {
- return;
+
+ // pull to refresh
+ if (isPullToRefreshEnabled_) {
+ if (isReloading_) {
+ // Update the content inset, good for section headers
+ if (scrollView.contentOffset.y > 0) {
+ self.tableView.contentInset = UIEdgeInsetsZero;
+ }
+ else if (scrollView.contentOffset.y >= -kRCLRefreshHeaderHeight) {
+ self.tableView.contentInset = UIEdgeInsetsMake(-scrollView.contentOffset.y, 0, 0, 0);
+ }
+ return;
+ } else if (isDragging_ && scrollView.contentOffset.y < 0) {
+ // User is scrolling above the header
+ if (scrollView.contentOffset.y < -kRCLRefreshHeaderHeight) {
+ [refreshHeaderView_ setState:RCLTableRefreshStateDragging];
+ }
+ // User is scrolling somewhere within the header
+ else {
+ [refreshHeaderView_ setState:RCLTableRefreshStateNormal];
+ }
+ return;
+ }
}
- NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
- if ([indexPaths count]) {
- NSIndexPath *indexPath = [indexPaths objectAtIndex:[indexPaths count]-1];
+ if (isPaginationEnabled_) {
+ // pagination
+ if (![dataSource_ count]) {
+ return;
+ }
+ NSArray *indexPaths = [self.tableView indexPathsForVisibleRows];
- if (indexPath.row >= [dataSource_ count] - 5) {
- if (!isLoadingNextPage_ && morePagesAreAvailable_) {
- [self startLoadingResults:kRCLTableViewResultsPerPage fromIndex:[dataSource_ count]];
+ if ([indexPaths count]) {
+ NSIndexPath *indexPath = [indexPaths objectAtIndex:[indexPaths count]-1];
+
+ if (indexPath.row >= [dataSource_ count] - 5) {
+ if (!isLoadingNextPage_ && morePagesAreAvailable_) {
+ [self startLoadingResults:kRCLTableViewResultsPerPage fromIndex:[dataSource_ count]];
+ }
}
}
}
}
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
- return 0;
-}
-
-- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
- return nil;
+- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
+
+ if (isPullToRefreshEnabled_) {
+ if (isReloading_) return;
+ isDragging_ = NO;
+
+ if (scrollView.contentOffset.y <= -kRCLRefreshHeaderHeight) {
+ // Released above the header
+ [self startReloadingResults];
+ }
+ }
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
@@ -116,7 +227,25 @@ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
}
}
+#pragma mark -
+#pragma mark UITableViewDataSource
+- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
+ return 0;
+}
+
+- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
+ return nil;
+}
+
+#pragma -
+#pragma Memory management
- (void)viewDidUnload {
+ [super viewDidUnload];
+ // clean up work that has been done in the viewDidLoad method
+ if (refreshHeaderView_) {
+ [refreshHeaderView_ removeFromSuperview];
+ refreshHeaderView_ = nil;
+ }
}
- (void)dealloc {
@@ -125,6 +254,7 @@ - (void)dealloc {
loadingView_ = nil;
self.dataSource = nil;
self.tableView = nil;
+ self.lastRefreshDate = nil;
[super dealloc];
}
View
6 RCL.xcodeproj/project.pbxproj
@@ -39,6 +39,7 @@
3B5026B5133AB15100F2A350 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B5026B3133AB13500F2A350 /* QuartzCore.framework */; };
3B814C2F133D3FDA00EAB1F1 /* RCLRefreshTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B814C2E133D3FDA00EAB1F1 /* RCLRefreshTableViewController.m */; };
3B814C30133D3FDA00EAB1F1 /* RCLRefreshTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B814C2E133D3FDA00EAB1F1 /* RCLRefreshTableViewController.m */; };
+ 3B8157E91485090900B461CB /* RCLTableRefreshHeader.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B8157E81485090900B461CB /* RCLTableRefreshHeader.m */; };
3B82A00B134CFD3C00C06671 /* RCLMVCExample.m in Sources */ = {isa = PBXBuildFile; fileRef = 3B82A009134CFD3C00C06671 /* RCLMVCExample.m */; };
3B82A00C134CFD3C00C06671 /* RCLMVCExample.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B82A00A134CFD3C00C06671 /* RCLMVCExample.xib */; };
3B82A00F134CFD7900C06671 /* RCLMVCExample.xib in Resources */ = {isa = PBXBuildFile; fileRef = 3B82A00E134CFD7900C06671 /* RCLMVCExample.xib */; };
@@ -102,6 +103,8 @@
3B5026B3133AB13500F2A350 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
3B814C2D133D3FDA00EAB1F1 /* RCLRefreshTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCLRefreshTableViewController.h; sourceTree = "<group>"; };
3B814C2E133D3FDA00EAB1F1 /* RCLRefreshTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCLRefreshTableViewController.m; sourceTree = "<group>"; };
+ 3B8157E71485090900B461CB /* RCLTableRefreshHeader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCLTableRefreshHeader.h; sourceTree = "<group>"; };
+ 3B8157E81485090900B461CB /* RCLTableRefreshHeader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCLTableRefreshHeader.m; sourceTree = "<group>"; };
3B82A008134CFD3C00C06671 /* RCLMVCExample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCLMVCExample.h; sourceTree = "<group>"; };
3B82A009134CFD3C00C06671 /* RCLMVCExample.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCLMVCExample.m; sourceTree = "<group>"; };
3B82A00A134CFD3C00C06671 /* RCLMVCExample.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = RCLMVCExample.xib; sourceTree = "<group>"; };
@@ -172,6 +175,8 @@
3B8E32361316FB1200206157 /* RCLMemoryCache.m */,
3B92B5BB131AE1D9007AF47C /* RCLMapController.h */,
3B92B5BC131AE1D9007AF47C /* RCLMapController.m */,
+ 3B8157E71485090900B461CB /* RCLTableRefreshHeader.h */,
+ 3B8157E81485090900B461CB /* RCLTableRefreshHeader.m */,
3B5026AA133AA7F700F2A350 /* RCLTableViewController.h */,
3B5026AB133AA7F700F2A350 /* RCLTableViewController.m */,
3B814C2D133D3FDA00EAB1F1 /* RCLRefreshTableViewController.h */,
@@ -429,6 +434,7 @@
3B94D89213C4DCB700377EEA /* RCLMemoryCache.m in Sources */,
3B19F8521484243F0050E7F6 /* RCLModalPickerView.m in Sources */,
3B19F856148432AB0050E7F6 /* RCLModalPickerExample.m in Sources */,
+ 3B8157E91485090900B461CB /* RCLTableRefreshHeader.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Please sign in to comment.
Something went wrong with that request. Please try again.