iOS学习系列 - UITableView下拉更新/上提加载的实现

可以利用:http://www.cocoacontrols.com/platforms/ios/controls/ah3dpullrefresh 的Demo。

可以采用Category的方式,对于UITableView进行扩展(类似于动态创建PushView),例如UITableView+PushAndLoadRefresh.h;而UITableView只要调用扩展方法,通过block回调,重新刷新列表而达到目的。这样还使得UI上最大的松耦合。

  

 源代码如下:

UIScrollView+AH3DPullRefresh.h

#import <UIKit/UIKit.h>

@class AHPullToRefreshView;

@interface UIScrollView (AH3DPullRefresh)

@property (nonatomic, retain) AHPullToRefreshView * pullToRefreshView;
@property (nonatomic, retain) AHPullToRefreshView * pullToLoadMoreView;

#pragma mark - Init

/**
 Sets the pull to refresh handler. Call this method to initialize the pull to refresh view.
 @param handler The block to be executed when the pull to refresh view is pulled and released.
 
*/
- (void)setPullToRefreshHandler:(void (^)(void))handler;

/**
 Sets the pull to load more handler. Call this method to initialize the pull to load more view.
 @param handler The block to be executed when the pull to load more view is pulled and released.
 
*/
- (void)setPullToLoadMoreHandler:(void (^)(void))handler;

#pragma mark - Action

/**
 Pulls the scrollview to the top in order to refresh the contents.
 The intented use of this method is to pull refresh programatically.
 
*/
- (void)pullToRefresh;

/**
 Pulls the scrollview to the bottom in order to load more contents.
 The intented use of this methos is to pull to load more programmatically.
 
*/
- (void)pullToLoadMore;

/**
 Hides the pull refresh view. Use it to notify the pull refresh view that the content have been refreshed. 
 
*/
- (void)refreshFinished;

/**
 Hides the pull to load more view. Use it to notify the pull to load more view that the content have been refreshed. 
 
*/
- (void)loadMoreFinished;

#pragma mark - Customization

/**
 Returns the pull to refresh label.
 @return the pull to refresh label.
 
*/
- (UILabel *)pullToRefreshLabel;

/**
 Sets the pull to refresh view's background color. Default: white.
 @param backgroundColor The background color.
 
*/
- (void)setPullToRefreshViewBackgroundColor:(UIColor *)backgroundColor;

/**
 Sets the activity indicator style of the pull to refresh view.
 @param style The activity indicator style.
 
*/
- (void)setPullToRefreshViewActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style;

/**
 Sets the text when pulling the pull to refresh view.
 Default: NSLocalizedString(@"Continue pulling to refresh",@"")
 @param pullingText The text to display when pulling the view.
 
*/
- (void)setPullToRefreshViewPullingText:(NSString *)pullingText;

/**
 Sets the text when the pull to refresh view is pulled to the maximum to suggest the user release to refresh.
 Default: NSLocalizedString(@"Release to refresh",@"")
 @param releaseText The text to display to suggest the user release the scrollview.
 
*/
- (void)setPullToRefreshViewReleaseText:(NSString *)releaseText;

/**
 Sets the text when the pull to refresh view has been released to tell the user that the content is being loaded.
 Default: NSLocalizedString(@"Loading...",@"")
 @param loadingText The text to display while refreshing.
 
*/
- (void)setPullToRefreshViewLoadingText:(NSString *)loadingText;

/**
 Sets the text when the pull to refresh view has finished loading.
 Default: NSLocalizedString(@"Loaded!",@"")
 @param loadedText The text to display when the contents has been refreshed.
 
*/
- (void)setPullToRefreshViewLoadedText:(NSString *)loadedText;

/**
 Returns the pull to load more label.
 @return the pull to load more label.
 
*/
- (UILabel *)pullToLoadMoreLabel;

/**
 Sets the pull to load more view's background color. Default: white.
 @param backgroundColor The background color.
 
*/
- (void)setPullToLoadMoreViewBackgroundColor:(UIColor *)backgroundColor;

/**
 Sets the activity indicator style of the pull to load more view.
 @param style The activity indicator style.
 
*/
- (void)setPullToLoadMoreViewActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style;

/**
 Sets the text when pulling the pull to load more view.
 Default: NSLocalizedString(@"Continue pulling to refresh",@"")
 @param pullingText The text to display when pulling the view.
 
*/
- (void)setPullToLoadMoreViewPullingText:(NSString *)pullingText;

/**
 Sets the text when the pull to load more view is pulled to the maximum to suggest the user release to refresh.
 Default: NSLocalizedString(@"Release to refresh",@"")
 @param releaseText The text to display to suggest the user release the scrollview.
 
*/
- (void)setPullToLoadMoreViewReleaseText:(NSString *)releaseText;

/**
 Sets the text when the pull to load more view has been released to tell the user that the content is being loaded.
 Default: NSLocalizedString(@"Loading...",@"")
 @param loadingText The text to display while refreshing.
 
*/
- (void)setPullToLoadMoreViewLoadingText:(NSString *)loadingText;

/**
 Sets the text when the pull to load more view has finished loading.
 Default: NSLocalizedString(@"Loaded!",@"")
 @param loadedText The text to display when the contents has been refreshed.
 
*/
- (void)setPullToLoadMoreViewLoadedText:(NSString *)loadedText;

@end

 

 

UIScrollView+AH3DPullRefresh.m 

#import "UIScrollView+AH3DPullRefresh.h"

#import <objc/runtime.h>
#import <QuartzCore/QuartzCore.h>

// --------------------------------------------------------------------------------
#pragma mark - Helpers

#define AHRelease(object) [object release]; object = nil
#define CATransform3DPerspective(t, x, y) (CATransform3DConcat(t, CATransform3DMake(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, 0, 0, 0, 0, 1)))
#define CATransform3DMakePerspective(x, y) (CATransform3DPerspective(CATransform3DIdentity, x, y))

CG_INLINE CATransform3D CATransform3DMake(CGFloat m11, CGFloat m12, CGFloat m13, CGFloat m14,
                                          CGFloat m21, CGFloat m22, CGFloat m23, CGFloat m24,
                                          CGFloat m31, CGFloat m32, CGFloat m33, CGFloat m34,
                                          CGFloat m41, CGFloat m42, CGFloat m43, CGFloat m44) {
    CATransform3D t;
    t.m11 = m11; t.m12 = m12; t.m13 = m13; t.m14 = m14;
    t.m21 = m21; t.m22 = m22; t.m23 = m23; t.m24 = m24;
    t.m31 = m31; t.m32 = m32; t.m33 = m33; t.m34 = m34;
    t.m41 = m41; t.m42 = m42; t.m43 = m43; t.m44 = m44;
    return t;
}

// --------------------------------------------------------------------------------
#pragma mark - [Interface] AHPullToRefreshView

/**
 Defines the possible states of the pull to refresh view.
 
*/
typedef enum {
    AHPullViewStateHidden = 1,                // Not visible
    AHPullViewStateVisible,                   // Visible but won't trigger the loading if the user releases
    AHPullViewStateTriggered,                 // If the user releases the scrollview it will load
    AHPullViewStateTriggeredProgramatically,  // When triggering it programmatically
    AHPullViewStateLoading,                   // Loading
    AHPullViewStateLoadingProgramatically     // Loading when triggered programatically
} AHPullViewState;

static CGFloat const kAHPullView_ViewHeight = 60.0;

#define kAHPullView_ContentOffsetKey    @"contentOffset"
#define kAHPullView_FrameKey            @"frame"

@interface AHPullToRefreshView : UIView {
    
    AHPullViewState _state;                         // Current state

    UIScrollView * _scrollView;                     // The linked scrollview
    BOOL _isObservingScrollView;                    // If it's observing (KVO) the scrollview
    UIEdgeInsets _originalScrollViewContentInset;   // The original content inset of the scrollview
    
    UIColor * _backgroundColor;                     // The view's background color
    
    UIView * _backgroundView;                       // The background view
    UIView * _shadowView;                           // The view that applies the shadow (black with changing alpha)
    UILabel * _label;                               // Where to display the texts depending on the state
    UIActivityIndicatorView * _activityIndicator;   // Shown while loading
    
    NSString * _pullingText;                        // Customization
    NSString * _releaseText;
    NSString * _loadingText;
    NSString * _loadedText;
}

@property (nonatomic, assign) BOOL isObservingScrollView;   // If it's observing (KVO) the scrollview

@property (nonatomic, retain) UILabel * label;

@property (nonatomic, retain) NSString * pullingText;       // Displayed in _label while pulling
@property (nonatomic, retain) NSString * releaseText;       // Displayed in _label before releasing
@property (nonatomic, retain) NSString * loadingText;       // Displayed in _label while loading
@property (nonatomic, retain) NSString * loadedText;        // Displayed in _label when loading did finish

@property (nonatomic, copy) void (^pullToRefreshHandler)(void);   // The block executed when triggering pull refresh
@property (nonatomic, copy) void (^pullToLoadMoreHandler)(void);  // The block executed when triggering pull load more

/**
 Initializes the view with the linked scrollview.
 @param scrollView The scrollview where to apply the pull refresh view.
 
*/
- (id)initWithScrollView:(UIScrollView *)scrollView;

/**
 Pulls the scrollview to refresh the contents, scrolling it up.
 The intented use of this method is to pull refresh programatically.
 
*/
- (void)pullToRefresh;

/**
 Hides the pull refresh view. Use it to notify the pull refresh view that the content have been refreshed. 
 
*/
- (void)refreshFinished;

/**
 Sets the background color.
 @param backgroundColor the background color.
 
*/
- (void)setBackgroundColor:(UIColor *)backgroundColor;

/**
 Sets the activity indicator style, displayed while loading.
 @param style the activity indicator style.
 
*/
- (void)setActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style;

@end

// --------------------------------------------------------------------------------
#pragma mark - [Interface] AHPullToRefreshView (Private)

@interface AHPullToRefreshView (Private)

- (void)startObservingScrollView;
- (void)stopObservingScrollView;

- (void)scrollViewDidScroll:(CGPoint)contentOffset;
- (void)setScrollViewContentInset:(UIEdgeInsets)contentInset;
- (UIEdgeInsets)scrollViewContentInset;
- (void)setState:(AHPullViewState)state;
- (void)layoutSubviews:(NSTimer *)timer;

- (void)pullAfterProgrammaticScroll;
- (void)layoutSubviewsToMaxFraction;

@end

// --------------------------------------------------------------------------------
#pragma mark - [Interface] AHTableView (Private)

@interface UIScrollView (AHTableViewPrivate)

@property (nonatomic, assign) BOOL isPullToRefreshEnabled;
@property (nonatomic, assign) BOOL isPullToLoadMoreEnabled;

@end 

// --------------------------------------------------------------------------------
#pragma mark - AHPullToRefreshView

@implementation AHPullToRefreshView

@synthesize isObservingScrollView = _isObservingScrollView;

@synthesize label = _label;

@synthesize pullingText = _pullingText;
@synthesize releaseText = _releaseText;
@synthesize loadingText = _loadingText;
@synthesize loadedText = _loadedText;

@synthesize pullToRefreshHandler;
@synthesize pullToLoadMoreHandler;

#pragma mark - View lifecycle

- (id)initWithScrollView:(UIScrollView *)scrollView {
    
    self = [super initWithFrame:CGRectMake(0, -kAHPullView_ViewHeight/2, _scrollView.bounds.size.width, kAHPullView_ViewHeight)];

    if (self) {
        
        // View setup
        [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth];
        
        // Ivars init
        _scrollView = scrollView;
        _originalScrollViewContentInset = [_scrollView contentInset];
        [self setBackgroundColor:[UIColor whiteColor]];

        _pullingText = [NSLocalizedString(@"Continue pulling to refresh",@"") retain];
        _releaseText = [NSLocalizedString(@"Release to refresh",@"") retain];
        _loadingText = [NSLocalizedString(@"Loading...",@"") retain];
        _loadedText = [NSLocalizedString(@"Loaded!",@"") retain];
    }
    return self;
}

- (void)dealloc {
    
    [self stopObservingScrollView];
    
    AHRelease(_backgroundView);
    AHRelease(_shadowView);
    self.label = nil;
    AHRelease(_activityIndicator);
    AHRelease(_backgroundColor);
    
    self.pullingText = nil;
    self.releaseText = nil;
    self.loadingText = nil;
    self.loadedText = nil;
    
    self.pullToRefreshHandler = nil;
    
    [super dealloc];
}

#pragma mark - Public methods

- (void)pullToRefresh {

    // If the it's actually loading or being pulled we avoid loading
    if (_state != AHPullViewStateHidden) {
        return;
    }
    
    // Stop observing scrollview
    [self stopObservingScrollView];
    
    // If pull to load more is not enabled then scroll to top
    BOOL isPullToLoadMoreEnabled = [_scrollView isPullToLoadMoreEnabled];
    if (!isPullToLoadMoreEnabled) {
        [_scrollView scrollRectToVisible:CGRectMake(00, CGRectGetWidth([_scrollView frame]), CGRectGetHeight([_scrollView frame])) animated:YES];
    }

    // Set the state to triggered programmatically
    [self setState:AHPullViewStateTriggeredProgramatically];
    
    // If it's triggered programatically avoid the user interaction
    [_scrollView setScrollEnabled:NO];
        
    // The delay to prevent overlapping animations. It will be between 0.1 and 0.5 seconds
    CGFloat delay = MAX(MIN(_scrollView.contentOffset.y/CGRectGetHeight([_scrollView frame]),0.1),0.5);
    [self performSelector:@selector(pullAfterProgrammaticScroll) withObject:nil afterDelay:delay];    
}

- (void)pullToLoadMore {
    
    // If the it's actually loading or being pulled we avoid loading
    if (_state != AHPullViewStateHidden) {
        return;
    }
    
    // Stop observing scrollview
    [self stopObservingScrollView];
    
    // If pull to refresh is not enabled then scroll to bottom
    BOOL isPullToRefreshEnabled = [_scrollView isPullToRefreshEnabled];
    if (!isPullToRefreshEnabled) {
        CGRect rect = CGRectMake(0, _scrollView.contentSize.height - CGRectGetHeight([_scrollView frame]), CGRectGetWidth([_scrollView frame]), CGRectGetHeight([_scrollView frame]));
        [_scrollView scrollRectToVisible:rect animated:YES];
    }
    
    // Set the state to triggered programmatically
    [self setState:AHPullViewStateTriggeredProgramatically];
    
    // If it's triggered programatically avoid the user interaction
    [_scrollView setScrollEnabled:NO];
    
    // The delay to prevent overlapping animations. It will be between 0.1 and 0.5 seconds
    CGFloat delay = MAX(MIN(_scrollView.contentOffset.y/_scrollView.contentSize.height,0.1),0.5);
    [self performSelector:@selector(pullAfterProgrammaticScroll) withObject:nil afterDelay:delay];
}

- (void)refreshFinished {
    
    // Set the state to hidden with a delay
    AHPullViewState state = AHPullViewStateHidden;
    SEL selector = @selector(setState:);
    NSMethodSignature *ms = [self methodSignatureForSelector:selector];
    NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:ms];
    [invocation setTarget:self];
    [invocation setSelector:selector];
    [invocation setArgument:&state atIndex:2];
    
    // Note: if called programatically quickly has a visual bug that the unfolding of the 3d view remains stuck. That's why there's a delay.
    if (_state == AHPullViewStateLoadingProgramatically) {
        [invocation performSelector:@selector(invoke) withObject:nil afterDelay:0.3];
    }
    else {
        [invocation performSelector:@selector(invoke) withObject:nil afterDelay:0.0];
    }
    
    // Apply an alpha anim to the view
    [UIView animateWithDuration:0.3 
                     animations:^{[_scrollView pullToLoadMoreView].alpha = 0;}
                     completion:^(BOOL finished){ if (finished) { [_scrollView pullToLoadMoreView].alpha = 1;}}];
    
    // Show the user the scroll indicators
    [_scrollView performSelector:@selector(flashScrollIndicators) withObject:nil afterDelay:0.35];
}

- (void)setPullToRefreshHandler:(void (^)(void))handler {
    
    [pullToRefreshHandler release];
    pullToRefreshHandler = [handler copy];
    
    // UI setup
    [_scrollView addSubview:self];
    [_scrollView sendSubviewToBack:self];
    
    _backgroundView = [[UIView alloc] initWithFrame:CGRectMake(00, CGRectGetWidth(_scrollView.frame), CGRectGetHeight(self.frame))];
    [_backgroundView.layer setAnchorPoint:CGPointMake(0.51.0)];
    [self.layer addSublayer:_backgroundView.layer];
    
    _shadowView = [[UIView alloc] initWithFrame:CGRectMake(00, CGRectGetWidth(_scrollView.frame), CGRectGetHeight(self.frame))];
    [_shadowView.layer setAnchorPoint:CGPointMake(0.51.0)];
    [self.layer addSublayer:_shadowView.layer];
    
    _label = [[UILabel alloc] initWithFrame:[_backgroundView frame]];
    _label.text = _loadedText;
    _label.font = [UIFont boldSystemFontOfSize:14];
    [_label setTextAlignment:UITextAlignmentCenter];
    _label.backgroundColor = [UIColor clearColor];
    _label.textColor = [UIColor darkGrayColor];
    [_label setCenter:_backgroundView.center];    
    [self addSubview:_label];
    
    _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    _activityIndicator.hidesWhenStopped = YES;
    [self addSubview:_activityIndicator];
    
    // Set the state to hidden
    [self setState:AHPullViewStateHidden];
}

- (void)setPullToLoadMoreHandler:(void (^)(void))handler {
    
    [pullToLoadMoreHandler release];
    pullToLoadMoreHandler = [handler copy];
    
    // UI setup
    [_scrollView addSubview:self];
    
    _backgroundView = [[UIView alloc] initWithFrame:CGRectMake(00, CGRectGetWidth(_scrollView.frame), CGRectGetHeight(self.frame))];
    [_backgroundView.layer setAnchorPoint:CGPointMake(0.51.0)];
    [self.layer addSublayer:_backgroundView.layer];
    
    _shadowView = [[UIView alloc] initWithFrame:CGRectMake(00, CGRectGetWidth(_scrollView.frame), CGRectGetHeight(self.frame))];
    [_shadowView.layer setAnchorPoint:CGPointMake(0.51.0)];
    [self.layer addSublayer:_shadowView.layer];
    
    _label = [[UILabel alloc] initWithFrame:[_backgroundView frame]];
    _label.text = _loadedText;
    _label.font = [UIFont boldSystemFontOfSize:14];
    [_label setTextAlignment:UITextAlignmentCenter];
    _label.backgroundColor = [UIColor clearColor];
    _label.textColor = [UIColor darkGrayColor];
    [_label setCenter:_backgroundView.center];    
    [self addSubview:_label];
    
    _activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
    _activityIndicator.hidesWhenStopped = YES;
    [self addSubview:_activityIndicator];
    
    // Set the state to hidden
    [self setState:AHPullViewStateHidden];
}

// This method is actually an UIView override
- (void)setBackgroundColor:(UIColor *)backgroundColor {
    
    // Set the color for the background view instead of the actual view
    [_backgroundColor release];
    _backgroundColor = [backgroundColor retain];
    [_backgroundView setBackgroundColor:_backgroundColor];
}

- (void)setActivityIndicatorStyle:(UIActivityIndicatorViewStyle)style {

    [_activityIndicator setActivityIndicatorViewStyle:style];

 

 调用代码:

    // Set the pull to refresh handler block
    [_tableView setPullToRefreshHandler:^{
        
        /**
         Note: Here you should deal perform a webservice request, CoreData query or 
         whatever instead of this dummy code ;-)
         
*/
        NSArray * newRows = [NSArray arrayWithObjects:
                             [kDataArray objectAtIndex:rand()%33],
                             [kDataArray objectAtIndex:rand()%33], nil];
        [self performSelector:@selector(dataDidRefresh:) withObject:newRows afterDelay:5.0];
    }];
    
    // Set the pull to laod more handler block
    [_tableView setPullToLoadMoreHandler:^{
        
        /**
         Note: Here you should deal perform a webservice request, CoreData query or 
         whatever instead of this dummy code ;-)
         
*/
        NSArray * newRows = [NSArray arrayWithObjects:
                             [kDataArray objectAtIndex:rand()%33],
                             [kDataArray objectAtIndex:rand()%33], nil];
        [self performSelector:@selector(dataDidLoadMore:) withObject:newRows afterDelay:5.0];

    }]; 

 


posted @ 2012-10-17 14:11  Leepy  阅读(8555)  评论(0编辑  收藏  举报