#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
#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(
0,
0, 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(
0,
0, CGRectGetWidth(_scrollView.frame), CGRectGetHeight(self.frame))];
[_backgroundView.layer setAnchorPoint:CGPointMake(
0.5,
1.0)];
[self.layer addSublayer:_backgroundView.layer];
_shadowView = [[UIView alloc] initWithFrame:CGRectMake(
0,
0, CGRectGetWidth(_scrollView.frame), CGRectGetHeight(self.frame))];
[_shadowView.layer setAnchorPoint:CGPointMake(
0.5,
1.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(
0,
0, CGRectGetWidth(_scrollView.frame), CGRectGetHeight(self.frame))];
[_backgroundView.layer setAnchorPoint:CGPointMake(
0.5,
1.0)];
[self.layer addSublayer:_backgroundView.layer];
_shadowView = [[UIView alloc] initWithFrame:CGRectMake(
0,
0, CGRectGetWidth(_scrollView.frame), CGRectGetHeight(self.frame))];
[_shadowView.layer setAnchorPoint:CGPointMake(
0.5,
1.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];
}