BuySellAds.com

If you like my tutorials, you will love my book . It is chockful of advanced programming techniques and the only comprehensive barcode reference for serious iOS developers.
Our DNA is written in Objective-C
Jump

How to make a Pull-To-Reload TableView just like Tweetie 2

When I started on Twitter, I tried out a few Twitter clients both on Mac and iPhone until I quickly settled on Tweetie. When Loren Brichter made the bold move to sell Tweetie 2 as a seperate app I also purchased it because I am convinced this guy means quality and Tweetie 2 is on the first page of my springboard.

One thing that’s cool about Tweetie 2 is the fresh paradigm to refreshing the contents of a table view. Up until now we had been looking for space to mount a reload button on, sometimes having to resort to adding an extra tool bar for just one view so that you can have enough space. Now if you have a tableview that it sorted reverse chronologically, then you have a natural urge to make new items appear at the top by pulling down the table with extra force.

Loren recognized this need and innovated the Pull-To-Reload paradigm. If you want to refresh a tableview in Tweetie 2 then you simply pull down the table far enough for an additional cell to appear at the top with the instruction “Pull down to refresh”. If you do, then at a certain point the arrow rotates and the text changes to “Release to refresh”. All accompanied by two distinct wooshing sounds and a pop once the reloading action has ceased. The Intuitiveness of this paradigm is so compelling in fact that people who use Tweetie 2 start to try to refresh ALL tableviews like this.

Might be a good case to make this the standard way from now on because it feels more logical and natural than to tap on a small button with a circular arrow on it. A user of MyAppSales requested that I add this mechanism for reloading reviews of individual apps. At first I thought this to be advanced magic, probably using forbidden techniques. But after a bit of research and lots of hints coming from my Twitter friends (thanks Thomas and Fabian) I figured it out. This article explains how I did it.

At first I experimented a bit myself and found that if you add a subview to a tableview then this moves together with it. But I could not find any way to make the contents of this extra view change depending on where it was on the screen. So I asked for help and help I got. The deciding hint was to have a look at Devin Doty’s (enormego)¬†implementation of this.

So thank you to Devin for laying the groundwork. The first bit of knowledge that was necessary was to understand that UITableView inherits from UIScrollView and thus also receives all the scroll view delegate messages. The 3 magic ingredients are scrollViewWillBeginDragging, scrollViewDidScroll and scrollViewDidEndDragging. Once you know that these are called you cannot but marvel at the ingeniousness. The checkForRefresh BOOL keeps track if dragging has started so that in all other cases scrolling can be ignored. And the reloading BOOL is YES if the reloading animation is being shown.

The second piece of the puzzle is how to make the refresh view stay visible during reloading. This is achieved by setting the edge insets of the table to a negative value. And when the reload is done to set them back to zero. All in animation blocks so that it does not jump but implicitly animates to the new state.

Devin’s implementation consists of two classes. EGORefreshTableHeaderView is added as a subview of EGOTableViewPullRefresh. The latter subclasses UITableView and has to have the delegate pointing to itself so that it can receive the scrolling events.

This is bad form in my humble opinion. When I tried to simply copy/paste Devin’s code into MyAppSales I found that I had a problem due to this delegate bending. In order to have custom heights of my cells on the review tableview I needed to implement tableView:heightForRowAtIndexPath. This is part of the delegate protocol, but with Devin’s approach my view controller is never called to get this height. The same is true for all other delegate methods. The approach I have seen other people take to work around this problem is to override and forward all delegate methods to a custom delegate. So you get lots of unreadable code and a general feeling of yuck yuck.

Furthermore the EGOTableViewPullRefresh class saves the last updated date in the user preferences and generally does too much interact with data for the Model-View-Controller way of coding. Interaction with data (M) is supposed to be handled by a table view controller (C) and not the table view itself (V). So that had to go, and while I was at it, I changed the date formatter to use the system locale instead of hard coding the format.

So I did it the “proper way” by NOT subclassing UITableView, but UITableViewController instead. By creating your own PullToRefreshTableViewController you no longer have to resort to trickery in forwarding delegate method calls. In fact the only thing you need to do to change one table view into one supporting this reloading is to change the class from UITableViewController to PullToRefreshTableViewController. This simplifies the whole affair tremendously.

Another problem I found when playing around with Devin’s code was that I managed to get into a strange state where during reload the arrow would show and after it finished the activity indicator became visible. The reason is that the method to toggle between states assumes that only flip-flop-flip is possible. So I added a parameter to force it into the appropriate state even if you toss the tableview around.

- (void)toggleActivityView:(BOOL)isON
{
	if (!isON)
	{
		[activityView stopAnimating];
		arrowImage.hidden = NO;
	}
	else
	{
		[activityView startAnimating];
		arrowImage.hidden = YES;
		[self setStatus:kLoadingStatus];
	}
}

I also added a line of code to ignore scrolling events while reloading is taking place to additionally prevent getting into an inconsistent state:

if (reloading) return;

Devin’s project comes with a look that is almost identical to Tweetie 2, even though I feel that the arrow is a bit too large. But there are 3 colors of arrows to choose from. The final touch is to find 3 wav files to use. Here I initially wrote about borrowing sounds from Tweetie 2 which caused a major outcry of people. So please don’t. Why not just make your own sounds to underline your app’s uniqueness? Or simply forget about the sounds, Apple recommends you either have sound effects throughout your app or not at all.

Now enough talk, let me show you my code. Please forgive me for inserting so many extra line breaks so that the code will fit into the code boxes. If you don’t want to copy/paste it, then just grab the files from the MyAppSales trunk. EGORefreshTableHeaderView needed only minor modifications:

EGORefreshTableHeaderView.h

//
//  EGORefreshTableHeaderView.h
//  Demo
//
//  Created by Devin Doty on 10/14/09October14.
//  Copyright 2009 enormego. All rights reserved.
//
 
#import <UIKit/UIKit.h&>
 
@interface EGORefreshTableHeaderView : UIView {
 
	UILabel *lastUpdatedLabel;
	UILabel *statusLabel;
	UIImageView *arrowImage;
	UIActivityIndicatorView *activityView;
 
	BOOL isFlipped;
 
	NSDate *lastUpdatedDate;
}
@property BOOL isFlipped;
 
@property (nonatomic, retain) NSDate *lastUpdatedDate;
 
- (void)flipImageAnimated:(BOOL)animated;
- (void)toggleActivityView:(BOOL)isON;
- (void)setStatus:(int)status;
 
@end

In the implementation I also replaced setCurrentDate with a property because the last updated date is not necessarily the current one. Each of the apps you a tracking with MyAppSales can have a different time you last updated the reviews of it. The labels no longer have a clear background but instead the same background color as the whole view. It does not do much for performance in this case, but in general you should make your labels opaque so that less compositing is going on.

EGORefreshTableHeaderView.m

//
//  EGORefreshTableHeaderView.m
//  Demo
//
//  Created by Devin Doty on 10/14/09October14.
//  Copyright 2009 enormego. All rights reserved.
//
 
#import "EGORefreshTableHeaderView.h"
#import <QuartzCore/QuartzCore.h>
 
#define kReleaseToReloadStatus	0
#define kPullToReloadStatus		1
#define kLoadingStatus			2
 
#define TEXT_COLOR [UIColor colorWithRed:0.341 green:0.737 blue:0.537 alpha:1.0]
#define BORDER_COLOR [UIColor colorWithRed:0.341 green:0.737 blue:0.537 alpha:1.0]
 
@implementation EGORefreshTableHeaderView
 
@synthesize isFlipped, lastUpdatedDate;
 
- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame])
	{
		self.backgroundColor = [UIColor colorWithRed:226.0/255.0
				green:231.0/255.0 blue:237.0/255.0 alpha:1.0];
 
		lastUpdatedLabel = [[UILabel alloc] initWithFrame:
			CGRectMake(0.0f, frame.size.height - 30.0f,
			 320.0f, 20.0f)];
		lastUpdatedLabel.font = [UIFont systemFontOfSize:12.0f];
		lastUpdatedLabel.textColor = TEXT_COLOR;
		lastUpdatedLabel.shadowColor =
			 [UIColor colorWithWhite:0.9f alpha:1.0f];
		lastUpdatedLabel.shadowOffset = CGSizeMake(0.0f, 1.0f);
		lastUpdatedLabel.backgroundColor = self.backgroundColor;
		lastUpdatedLabel.opaque = YES;
		lastUpdatedLabel.textAlignment = UITextAlignmentCenter;
		[self addSubview:lastUpdatedLabel];
		[lastUpdatedLabel release];
 
		statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f,
				 frame.size.height - 48.0f, 320.0f, 20.0f)];
		statusLabel.font = [UIFont boldSystemFontOfSize:13.0f];
		statusLabel.textColor = TEXT_COLOR;
		statusLabel.shadowColor = [UIColor colorWithWhite:0.9f alpha:1.0f];
		statusLabel.shadowOffset = CGSizeMake(0.0f, 1.0f);
		statusLabel.backgroundColor = self.backgroundColor;
		statusLabel.opaque = YES;
		statusLabel.textAlignment = UITextAlignmentCenter;
		[self setStatus:kPullToReloadStatus];
		[self addSubview:statusLabel];
		[statusLabel release];
 
		arrowImage = [[UIImageView alloc] initWithFrame:
			CGRectMake(25.0f, frame.size.height
			- 65.0f, 30.0f, 55.0f)];
		arrowImage.contentMode = UIViewContentModeScaleAspectFit;
		arrowImage.image = [UIImage imageNamed:@"blueArrow.png"];
		[arrowImage layer].transform =
			CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f);
		[self addSubview:arrowImage];
		[arrowImage release];
 
		activityView = [[UIActivityIndicatorView alloc]
			initWithActivityIndicatorStyle:
			UIActivityIndicatorViewStyleGray];
		activityView.frame = CGRectMake(25.0f, frame.size.height
			- 38.0f, 20.0f, 20.0f);
		activityView.hidesWhenStopped = YES;
		[self addSubview:activityView];
		[activityView release];
 
		isFlipped = NO;
    }
    return self;
}
 
- (void)drawRect:(CGRect)rect{
	CGContextRef context = UIGraphicsGetCurrentContext();
	CGContextDrawPath(context,  kCGPathFillStroke);
	[BORDER_COLOR setStroke];
	CGContextBeginPath(context);
	CGContextMoveToPoint(context, 0.0f, self.bounds.size.height - 1);
	CGContextAddLineToPoint(context, self.bounds.size.width,
		self.bounds.size.height - 1);
	CGContextStrokePath(context);
}
 
- (void)flipImageAnimated:(BOOL)animated
{
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:animated ? .18 : 0.0];
	[arrowImage layer].transform = isFlipped ?
			CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f) :
			CATransform3DMakeRotation(M_PI * 2, 0.0f, 0.0f, 1.0f);
	[UIView commitAnimations];
 
	isFlipped = !isFlipped;
}
 
- (void)setLastUpdatedDate:(NSDate *)newDate
{
	if (newDate)
	{
		if (lastUpdatedDate != newDate)
		{
			[lastUpdatedDate release];
		}
 
		lastUpdatedDate = [newDate retain];
 
		NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
		[formatter setDateStyle:NSDateFormatterShortStyle];
		[formatter setTimeStyle:NSDateFormatterShortStyle];
		lastUpdatedLabel.text = [NSString stringWithFormat:
		@"Last Updated: %@", [formatter stringFromDate:lastUpdatedDate]];
		[formatter release];
	}
	else
	{
		lastUpdatedDate = nil;
		lastUpdatedLabel.text = @"Last Updated: Never";
	}
}
 
- (void)setStatus:(int)status
{
	switch (status) {
		case kReleaseToReloadStatus:
			statusLabel.text = @"Release to refresh...";
			break;
		case kPullToReloadStatus:
			statusLabel.text = @"Pull down to refresh...";
			break;
		case kLoadingStatus:
			statusLabel.text = @"Loading...";
			break;
		default:
			break;
	}
}
 
- (void)toggleActivityView:(BOOL)isON
{
	if (!isON)
	{
		[activityView stopAnimating];
		arrowImage.hidden = NO;
	}
	else
	{
		[activityView startAnimating];
		arrowImage.hidden = YES;
		[self setStatus:kLoadingStatus];
	}
}
 
- (void)dealloc
{
	activityView = nil;
	statusLabel = nil;
	arrowImage = nil;
	lastUpdatedLabel = nil;
    [super dealloc];
}
 
@end

And this is my re-implementation as view controller, with all the necessary modifications discussed above.

PullToRefreshTableViewController.h

//
//  PullToRefreshTableViewController.h
//  ASiST
//
//  Created by Oliver on 09.12.09.
//  Copyright 2009 Drobnik.com. All rights reserved.
//
 
#import <UIKit/UIKit.h>
#import "EGORefreshTableHeaderView.h"
#import "SoundEffect.h"
 
@interface PullToRefreshTableViewController : UITableViewController
{
	EGORefreshTableHeaderView *refreshHeaderView;
 
	BOOL checkForRefresh;
	BOOL reloading;
 
	SoundEffect *psst1Sound;
	SoundEffect *psst2Sound;
	SoundEffect *popSound;
}
 
- (void)dataSourceDidFinishLoadingNewData;
- (void) showReloadAnimationAnimated:(BOOL)animated;
 
@end

PullToRefreshTableViewController.m

//
//  PullToRefreshTableViewController.m
//  ASiST
//
//  Created by Oliver on 09.12.09.
//  Copyright 2009 Drobnik.com. All rights reserved.
//
 
#import "PullToRefreshTableViewController.h"
 
#define kReleaseToReloadStatus 0
#define kPullToReloadStatus 1
#define kLoadingStatus 2
 
@implementation PullToRefreshTableViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
 
	refreshHeaderView = [[EGORefreshTableHeaderView alloc] initWithFrame:
			CGRectMake(0.0f, 0.0f - self.view.bounds.size.height,
			320.0f, self.view.bounds.size.height)];
	[self.tableView addSubview:refreshHeaderView];
	self.tableView.showsVerticalScrollIndicator = YES;
 
	// pre-load sounds
	psst1Sound = [[SoundEffect alloc] initWithContentsOfFile:
				[[NSBundle mainBundle] pathForResource:@"psst1"
		ofType:@"wav"]];
	psst2Sound  = [[SoundEffect alloc] initWithContentsOfFile:
				[[NSBundle mainBundle] pathForResource:@"psst2"
		ofType:@"wav"]];
	popSound  = [[SoundEffect alloc] initWithContentsOfFile:
				[[NSBundle mainBundle] pathForResource:@"pop"
		ofType:@"wav"]];
 
}
 
- (void)dealloc
{
	[psst1Sound release];
	[psst2Sound release];
	[popSound release];
	[refreshHeaderView release];
    [super dealloc];
}
 
#pragma mark State Changes
 
- (void) showReloadAnimationAnimated:(BOOL)animated
{
	reloading = YES;
	[refreshHeaderView toggleActivityView:YES];
 
	if (animated)
	{
		[UIView beginAnimations:nil context:NULL];
		[UIView setAnimationDuration:0.2];
		self.tableView.contentInset = UIEdgeInsetsMake(60.0f, 0.0f, 0.0f,
			0.0f);
		[UIView commitAnimations];
	}
	else
	{
		self.tableView.contentInset = UIEdgeInsetsMake(60.0f, 0.0f, 0.0f,
			0.0f);
	}
}
 
- (void) reloadTableViewDataSource
{
	NSLog(@"Please override reloadTableViewDataSource");
}
 
- (void)dataSourceDidFinishLoadingNewData
{
	reloading = NO;
	[refreshHeaderView flipImageAnimated:NO];
	[UIView beginAnimations:nil context:NULL];
	[UIView setAnimationDuration:.3];
	[self.tableView setContentInset:UIEdgeInsetsMake(0.0f, 0.0f, 0.0f, 0.0f)];
	[refreshHeaderView setStatus:kPullToReloadStatus];
	[refreshHeaderView toggleActivityView:NO];
	[UIView commitAnimations];
	[popSound play];
}
 
#pragma mark Table view methods
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
 
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:
	(NSInteger)section
{
    return 0;
}
 
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView
	cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
 
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
		CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
				reuseIdentifier:CellIdentifier] autorelease];
    }
 
    // Set up the cell...
 
    return cell;
}
 
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:
	(NSIndexPath *)indexPath
{
    // Navigation logic may go here.
}
 
#pragma mark Scrolling Overrides
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
	if (!reloading)
	{
		checkForRefresh = YES;  //  only check offset when dragging
	}
}
 
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
	if (reloading) return;
 
	if (checkForRefresh) {
		if (refreshHeaderView.isFlipped
				&amp;&amp; scrollView.contentOffset.y &gt; -65.0f
				&amp;&amp; scrollView.contentOffset.y &lt; 0.0f
				&amp;&amp; !reloading) {
			[refreshHeaderView flipImageAnimated:YES];
			[refreshHeaderView setStatus:kPullToReloadStatus];
			[popSound play];
 
		} else if (!refreshHeaderView.isFlipped
				&amp;&amp; scrollView.contentOffset.y &lt; -65.0f) {
			[refreshHeaderView flipImageAnimated:YES];
			[refreshHeaderView setStatus:kReleaseToReloadStatus];
			[psst1Sound play];
		}
	}
}
 
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView
				 willDecelerate:(BOOL)decelerate
{
	if (reloading) return;
 
	if (scrollView.contentOffset.y &lt;= - 65.0f) {
		if([self.tableView.dataSource respondsToSelector:
				@selector(reloadTableViewDataSource)]){
			[self showReloadAnimationAnimated:YES];
			[psst2Sound play];
			[self reloadTableViewDataSource];
		}
	}
	checkForRefresh = NO;
}
 
@end

Now, to use this code to add pull-to-refresh to any existing tableview you simple change the table view controller’s class. This is from the class where I am using it, AppDetailViewController, also part of MyAppSales.

#import "PullToRefreshTableViewController.h"
 
@interface AppDetailViewController : PullToRefreshTableViewController
@end

In the implementation you only have to override the method that get’s called when reload should take place. When reloading is done you call dataSourceDidFinishLoadingNewData.

- (void)synchingDone:(NSNotification *)notification
{
	refreshHeaderView.lastUpdatedDate = myApp.lastReviewRefresh;
	[super dataSourceDidFinishLoadingNewData];
}
 
- (void)reloadTableViewDataSource
{
	[myApp getAllReviews];
}

Ah, one more thing … it could also be the case that a reload is already active and I want the reloading header be showing when the view appears. That’s why I have this new method showReloadAnimationAnimated: that accepts a parameter to either animate or not animate the showing of the header. In our view controllers viewWillAppear I am calling it without animation:

if (alreadyReloading)
	[self showReloadAnimationAnimated:NO];

If you already donated for MyAppSales then simply update your working copy from trunk to get this code and all used resources, the images, the sounds and the SoundEffect class. If you don’t yet support my work, why not start today?

I encourage you to make use of this paradigm (and code) in your own projects. Let me know how this works out for you.

* UPDATE Dec 12th: Due to an outcry on the blogosphere over my using the wave files from Tweetie I need to point out that I don’t condone “repurposing” other apps’ resources in your own commercial apps. The point was to show that it’s a possible and I am doing it in an educational context. The other reason I can do that is that MyAppSales is not a commercial app. Otherwise it would be on the app store. If and when I am using the PullToRefreshTableviewController in a commercial app then I will have to make my own sounds or get permission (aka “license”) from Loren to use them.


Categories: Recipes

53 Comments »

  1. Sadly I’m a beginner and can’t get this to work. Tried to copy and paste the posted code into the demo created by Devin Doty and I keep running into issues.

    I can’t seem to be able to get the right SoundEffects wrapper, I can’t seem to comprehend if the new code provided functions in conjunction with Devin’s demo, etc.

    Again, I’m new to this and just trying to learn. Can somebody help?

    Thanks.

  2. If I where to package all of this into a component of Dr. Touch’s Parts Store, would you buy it, for – say – 50 Euros? http://www.drobnik.com/touch/2010/01/dr-touchs-parts-store/

  3. I don’t know about 50 Euros. That would be a bit steep for me, considering that I am just trying to learn and I’m just intrigued about this approach. I’m not using it to implement it on an app or anything like that.

    I just want to, more or less, compare the improvements that you made to the implementation on Devin’s demo. I just don’t know enough yet to piece things together on my own, but I’m working on it tho.

    Aside from that, considering that your is an implementation inspired by, and based on, Devin’s code, it would be somewhat questionable or perhaps controversial to charge for that code.

    But if I were to put a price that I could pay for someting like that, and again, considering my intended purpose, I would say that perhaps 10 or 15 Euros would be something that I could afford.

    That does NOT mean that the code is worth that. Not at all. It is worth more, I know. Knowledge is NOT cheap. I am just basing my appraisal on what I could afford based on my needs. That is all, so please do not get offended by the number.

    Thanks

  4. Do you have an idea why this is not triggering any events if implemented in UIViewController:

    @interface ListViewController : UIViewController {
    CommunityClient* communityClient;
    EGORefreshTableHeaderView *_refreshHeaderView;
    IBOutlet UITableView *messagesTable;

    I have a TabBar-Controller and so I am having the UITableView in this UIViewController…

    Thanks…

  5. I’d have to see the full code, possibly in a demo app to answer that.

  6. I am a new iPhone developer. Do you have ans example of how to navigate from a UIScrollView to a UITableView

    Thanks

  7. Usually you would have a navigationcontroller and then you push the table view controller on top of the controller with the scrolliview.

  8. Hey man! First of all, great tutorial. I’m trying to implement this here, but can’t get it to work.
    I made a simple app, just to try this. It is a UINavigationController, and inside there is the RootViewController.

    The RootViewController then inherits from PullToRefreshTableViewController and re-implement all UITableViewController Delegate and DataSource methods. But none of them are being called.
    Even those inside PullToRefreshTableViewController aren’t being called.

    I already tried to set my tableview’s delegate and datasource manually to self, but with no success. Everything goes fine, the tableview is loaded and presented on the device, the animation works, the labels are being updated, just as it was supposed to. But the UITableViewController methods wont get called, so I can’t populate and interact with my tableView. Do you know what is going wrong here?

    Thanks a lot man,

    Abras

  9. I would have to look at the code and debug it to see why the datasource methods are not being called.

  10. Ok, found the reason why the datasource methods weren’t being called. I don’t know why, but the code I got had numberOfSections = 0. Then, the tableView would be empty, and no other methods would get called.

    But there is one thing I can’t mange to get working here. How can I reload my tableView without having it scrolling to the top after reloaded?Just like tweetie. If i’m reading row 3, and the tableview gets reloaded, this row stays at the same spot it was. All others are added above the first, without having the tableView scrolled to the top.

    To reload my data, I’m using,

    [self performSelector:@selector(dataSourceDidFinishLoadingNewData) withObject:nil afterDelay:2];

    But this scrolls to the top.

    Thanks,

    Abras

  11. Wow!

    Without any modification, errors or whatnot, I successfully implemented your code into my project!

    Thank you, thank you, thank you!

    DWR

  12. I don’t see in this code where the method setLastUpdatedDate is called. What am I missing?

  13. Have a look at MyAppsales on GitHub.

  14. i’d love a demo project. i agree with the comments above about not charging for it though. you’ve gone through the good effort of this sample code, which is great. Thank you! But it isn’t as seamless to place it, even into Devon’s code. I’d like to see the entire project and see how it works in one place. Just to prevent stupid typos or minor missed connections. I’ve been to github and such and it’s just not coming together. A lil help to finish this off would be super appreciated.

  15. What is the &amp?

  16. Replace these with & in general

  17. Thanks for the quick reply! Also, do I have to put a UIView behind the table view and set it to EGOrefreshTableheaderview?

    Thanks,
    GEORGE

  18. What is the myapp in refreshHeaderView.lastUpdatedDate = myApp.lastReviewRefresh;?

    Cheers,
    John

  19. that’s just an example setting the last refresh date.

  20. Thank you very much for this! Very clean implementation and very easy to integrate.

    Much appreciated! :)

  21. same here! :)

  22. Excelent work!!
    It was pretty straightforward to include it in my project, and worked like a charm.
    One small refactor I made was to change the approach of overriding the reloadTableViewDataSource, with a protocol with a mandatory method. Instead of overriding the method, just adhere to the protocol.

    -(void)tableView:(UITableView*) reloadTableViewDataSource;

    Thank you very much for sharing this!!

  23. I’m trying to convert this so that it does the same thing but appears at the bottom of the table instead of the top.
    I’m having some trouble:

    So far the changes I’ve made are these:

    *****
    – (void)viewDidLoad
    {
    [super viewDidLoad];

    refreshHeaderView = [[EGORefreshTableHeaderView alloc] initWithFrame:
    //ORIG CGRectMake(0.0f, self.view.bounds.size.height,
    // 320.0f, self.view.bounds.size.height)];
    CGRectMake(0.0f, self.view.bounds.size.height * 2,
    320.0f, self.view.bounds.size.height)];

    *******

    – (void) showReloadAnimationAnimated:(BOOL)animated
    {
    reloading = YES;
    [refreshHeaderView toggleActivityView:YES];

    if (animated)
    {
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationDuration:0.2];
    //ORIG self.tableView.contentInset = UIEdgeInsetsMake(60.0f, 0.0f, 0.0f, 0.0f);
    self.tableView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, 60.0f, 0.0f);

    [UIView commitAnimations];
    }
    else
    {
    //ORIG self.tableView.contentInset = UIEdgeInsetsMake(60.0f, 0.0f, 0.0f, 0.0f);
    self.tableView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, 60.0f, 0.0f);
    }
    *******

    //ORIG lastUpdatedLabel = [[UILabel alloc] initWithFrame: CGRectMake(0.0f, frame.size.height – 30.0f, 320.0f, 20.0f)];
    lastUpdatedLabel = [[UILabel alloc] initWithFrame: CGRectMake(0.0f, 30.0f, 320.0f, 20.0f)];

    //ORIG statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, frame.size.height – 48.0f, 320.0f, 20.0f)];
    statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 38.0f, 320.0f, 20.0f)];

    //ORIG activityView.frame = CGRectMake(25.0f, frame.size.height – 38.0f, 20.0f, 20.0f);
    activityView.frame = CGRectMake(25.0f, 38.0f, 20.0f, 20.0f);

    The only problem is that the height of the footer seems to be too tall…. I’m not sure what I’m missing. Any help would be great thanks!

  24. heightForFooterInSection delegate method?

  25. Adding that method did nothing. Not sure why it would since the original worked without a heightForHeader method…

    I think my problem is here:

    CGRectMake(0.0f, self.view.bounds.size.height * 2, 320.0f, self.view.bounds.size.height)];

    When I make the rect. I’m not sure what those coordinates mean exactly… I know they stand for x, y, width, and height… but I’m not sure how they make the view fit into the table….

  26. Works great! Thanks for sharing this.

  27. Great code – thanks for the implementation and clear explanation. Quick question, though: I’ve got a long refresh time, so I’m trying to let users scroll to other parts of the tableview while the refresh header is doing its thing. This works fine, except that my (single) section header doesn’t scroll with the tableview. Any ideas how to get the section header to behave normally? i.e. after the refreshheader is activated by the user pulling down, if the user then swipes up to view other cells of the tableview, then I need the section header to “stick” to the bottom of the refreshheaderview rather than stay marooned in the middle of the view. Thanks for any help.

  28. Im using this one.Having big problem.I use transparent background in EGORefreshTableHeaderView frame.problem is it dosent remove the loading text.It over lap with other text.If i use background color for frame then it is ok.any one can help

  29. If you subclass UITableViewController, how am I supposed to make this work if I have a bunch of UITableViews littered as subviews of normal UIViews all in an .xib file for a UIViewController?

Trackbacks

  1. A TableView Just Like Tweetie 2. Innovation and Trends.
  2. Dr. Touch #007 – “Shaken not Stirred” @ Dr. Touch
  3. adoption curve dot net » Blog Archive » links for 2010-02-04
  4. OmegaDelta » Blog Archive » How to make pull to reload tableview like Tweetie 2
  5. OmegaDelta » Blog Archive » Slide to reload
  6. iWyre
  7. links for 2010-03-05 « Mike's Blog
  8. links for 2010-03-05 « Mike's Blog
  9. links for 2010-03-05 « Mike's Blog
  10. iWyre
  11. “Drag-to-refresh” or “Pull-To-Reload TableView” « CODE POETRY
  12. links for 2010-05-30 | Alones world
  13. Pull-To-Reload UITableView at Under The Bridge
  14. Anonymous
  15. Pull to refresh - iPhone Dev SDK Forum
  16. Pull-to-Refresh
  17. Pull to Refresh | Pull to Refresh iphone | Pull to Refresh iphone 4 | Iphone Mobile Phones > Iphone 4 Prices > Iphone Phones
  18. table top reload Objective c | Thinkings..
  19. Dr. Touch #007 – "Shaken not Stirred" @ Cocoanetics
  20. Expanding/Collapsing TableView Sections | Cocoanetics
  21. Mobile Gmail Adds "Pull Down to Refresh" « Google « Technology « Theory Report
  22. Mobile Gmail Adds "Pull Down to Refresh" | RemoveWAT
  23. Mobile Gmail Adds “Pull Down to Refresh” | Tux Blog
  24. Mobile Gmail Adds “Pull Down to Refresh” | Google Operating System

Leave a Comment

%d bloggers like this: