From e28be0f32ffb08f8aafce1021dc7c2472921dc3d Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Sun, 24 Jun 2012 00:14:17 -0300 Subject: [PATCH 1/9] Add changes --- README | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README b/README index 86c57ff..30e1dc5 100644 --- a/README +++ b/README @@ -4,9 +4,12 @@ It is: - a pull-to-refresh implementation - very easy to implement - doesn't suck + - Supports ARC and Non-ARC + - Add to bottom or top + To implement it: - - add the four files (PullToRefreshView.{h,m}, arrow.png and arrow@2x.png) to your project + - add the five files (PullToRefreshView.{h,m}, ARCMacros.h, arrow.png and arrow@2x.png) to your project - add the Quartz framework to your project if you haven't done so yet - #import "PullToRefreshView.h" - add QuartzCore to your project From f3159f2878f202251fa1297fd0f2272605643143 Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Sun, 24 Jun 2012 00:14:44 -0300 Subject: [PATCH 2/9] Add changes --- PullToRefreshView.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/PullToRefreshView.h b/PullToRefreshView.h index f2b96ef..94a729a 100644 --- a/PullToRefreshView.h +++ b/PullToRefreshView.h @@ -28,6 +28,7 @@ #import #import +#import "ARCMacros.h" typedef enum { kPullToRefreshViewStateUninitialized = 0, @@ -41,9 +42,10 @@ typedef enum { @protocol PullToRefreshViewDelegate; @interface PullToRefreshView : UIView { - id delegate; + __unsafe_unretained id delegate; UIScrollView *scrollView; PullToRefreshViewState state; + BOOL isBottom; UILabel *subtitleLabel; UILabel *statusLabel; @@ -58,8 +60,10 @@ typedef enum { - (void)refreshLastUpdatedDate; - (id)initWithScrollView:(UIScrollView *)scrollView; +- (id)initWithScrollView:(UIScrollView *)scrollView atBottom:(BOOL)bottom; - (void)finishedLoading; - (void)beginLoading; +- (void)setStatusLabelText:(NSString *)text; - (void)containingViewDidUnload; @end From 8bd70543acfbedcd85dff1a7cf92b4f33cdab00a Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Sun, 24 Jun 2012 00:15:10 -0300 Subject: [PATCH 3/9] Add changes --- PullToRefreshView.m | 173 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 141 insertions(+), 32 deletions(-) diff --git a/PullToRefreshView.m b/PullToRefreshView.m index d98e115..6d15264 100644 --- a/PullToRefreshView.m +++ b/PullToRefreshView.m @@ -39,6 +39,12 @@ @interface PullToRefreshView (Private) @property (nonatomic, assign) PullToRefreshViewState state; +- (BOOL)isScrolledToVisible; +- (BOOL)isScrolledToLimit; +- (void)parkVisible; +- (void)handleDragWhileLoading; +- (void)updatePosition; + @end @implementation PullToRefreshView @@ -57,22 +63,35 @@ - (void)showActivity:(BOOL)show animated:(BOOL)animated { - (void)setImageFlipped:(BOOL)flipped { [UIView beginAnimations:nil context:NULL]; [UIView setAnimationDuration:kPullToRefreshViewAnimationDuration]; - arrowImage.transform = (flipped ? CATransform3DMakeRotation(M_PI * 2, 0.0f, 0.0f, 1.0f) : CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f)); + arrowImage.transform = (flipped ^ isBottom ? CATransform3DMakeRotation(M_PI * 2, 0.0f, 0.0f, 1.0f) : CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f)); [UIView commitAnimations]; } -- (id)initWithScrollView:(UIScrollView *)scroll { - CGRect frame = CGRectMake(0.0f, 0.0f - scroll.bounds.size.height, scroll.bounds.size.width, scroll.bounds.size.height); +- (id)initWithScrollView:(UIScrollView *)scroll atBottom:(BOOL)bottom { + CGFloat bottomOffset = (scroll.contentSize.height < scroll.bounds.size.height) ? scroll.bounds.size.height : scroll.contentSize.height; + CGFloat offset = bottom ? bottomOffset : 0.0f - scroll.bounds.size.height; + CGRect frame = CGRectMake(0.0f, offset, scroll.bounds.size.width, scroll.bounds.size.height); if ((self = [super initWithFrame:frame])) { - scrollView = [scroll retain]; + CGFloat visibleBottom = bottom ? -kPullToRefreshViewTriggerOffset : self.frame.size.height; + isBottom = bottom; + +#if __has_feature(objc_arc) + // ARC is On + scrollView = SAFE_ARC_RETAIN(scroll); +#else + // ARC is Off + scrollView = [scroll retain]; +#endif + + [scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:NULL]; self.autoresizingMask = UIViewAutoresizingFlexibleWidth; self.backgroundColor = kPullToRefreshViewBackgroundColor; - subtitleLabel = [[UILabel alloc] init]; - subtitleLabel.frame = CGRectMake(0.0f, frame.size.height - 30.0f, self.frame.size.width, 20.0f); + statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, visibleBottom - 48.0f, self.frame.size.width, 20.0f)]; + subtitleLabel.frame = CGRectMake(0.0f, visibleBottom - 30.0f, self.frame.size.width, 20.0f); subtitleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; subtitleLabel.font = [UIFont systemFontOfSize:12.0f]; subtitleLabel.textColor = kPullToRefreshViewSubtitleColor; @@ -93,12 +112,14 @@ - (id)initWithScrollView:(UIScrollView *)scroll { [self addSubview:statusLabel]; arrowImage = [[CALayer alloc] init]; - arrowImage.frame = CGRectMake(25.0f, frame.size.height - 60.0f, 24.0f, 52.0f); + UIImage *arrow = [UIImage imageNamed:@"arrow"]; + arrowImage.contents = (id)arrow.CGImage; + arrowImage.frame = CGRectMake(25.0f, visibleBottom + kPullToRefreshViewTriggerOffset + 5.0f, arrow.size.width, arrow.size.height); arrowImage.contentsGravity = kCAGravityResizeAspect; - arrowImage.contents = (id) [UIImage imageNamed:@"arrow"].CGImage; + [self setImageFlipped:NO]; activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - activityView.frame = CGRectMake(30.0f, frame.size.height - 38.0f, 20.0f, 20.0f); + activityView.frame = CGRectMake(30.0f, visibleBottom - 38.0f, 20.0f, 20.0f); [self addSubview:activityView]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 @@ -108,26 +129,44 @@ - (id)initWithScrollView:(UIScrollView *)scroll { #endif [self.layer addSublayer:arrowImage]; + [self setState:kPullToRefreshViewStateNormal]; } return self; } +- (id)initWithScrollView:(UIScrollView *)scroll { + return [self initWithScrollView:scroll atBottom:NO]; +} + + #pragma mark - #pragma mark Setters +- (void)setStatusLabelText:(NSString *)text { + [statusLabel setText:text]; +} + - (void)refreshLastUpdatedDate { NSDate *date = [NSDate date]; - if ([delegate respondsToSelector:@selector(pullToRefreshViewLastUpdated:)]) + if ([delegate respondsToSelector:@selector(pullToRefreshViewLastUpdated:)]) { date = [delegate pullToRefreshViewLastUpdated:self]; + } NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter setAMSymbol:@"AM"]; [formatter setPMSymbol:@"PM"]; [formatter setDateFormat:@"MM/dd/yy hh:mm a"]; subtitleLabel.text = [NSString stringWithFormat:@"Last Updated: %@", [formatter stringFromDate:date]]; - [formatter release]; + +#if __has_feature(objc_arc) + // ARC is On + SAFE_ARC_RELEASE(formatter); +#else + // ARC is Off + [formatter release]; +#endif } - (void)beginLoading { @@ -156,7 +195,7 @@ - (void)setState:(PullToRefreshViewState)state_ { scrollView.contentInset = UIEdgeInsetsZero; break; case kPullToRefreshViewStateNormal: - statusLabel.text = @"Pull down to refresh…"; + statusLabel.text = [NSString stringWithFormat:@"Pull %@ to refresh...", isBottom ? @"up" : @"down"]; [self showActivity:NO animated:NO]; [self setImageFlipped:NO]; scrollView.contentInset = UIEdgeInsetsZero; @@ -166,7 +205,11 @@ - (void)setState:(PullToRefreshViewState)state_ { statusLabel.text = @"Loading…"; [self showActivity:YES animated:YES]; [self setImageFlipped:NO]; - scrollView.contentInset = UIEdgeInsetsMake(fminf(-scrollView.contentOffset.y, -kPullToRefreshViewTriggerOffset), 0, 0, 0); + if ([delegate respondsToSelector:@selector(pullToRefreshViewShouldRefresh:)]) { + [delegate pullToRefreshViewShouldRefresh:self]; + } + //scrollView.contentInset = UIEdgeInsetsMake(fminf(-scrollView.contentOffset.y, -kPullToRefreshViewTriggerOffset), 0, 0, 0); + [self parkVisible]; break; default: break; @@ -175,6 +218,64 @@ - (void)setState:(PullToRefreshViewState)state_ { [self setNeedsLayout]; } +- (BOOL)isScrolledToVisible { + if (isBottom) { + BOOL scrolledBelowContent; + if (scrollView.contentSize.height < scrollView.frame.size.height) { + scrolledBelowContent = scrollView.contentOffset.y > 0.0f; + } else { + scrolledBelowContent = scrollView.contentOffset.y > (scrollView.contentSize.height - scrollView.frame.size.height); + } + return scrolledBelowContent && ![self isScrolledToLimit]; + } else { + BOOL scrolledAboveContent = scrollView.contentOffset.y < 0.0f; + return scrolledAboveContent && ![self isScrolledToLimit]; + } +} + +- (BOOL)isScrolledToLimit { + if (isBottom) { + if (scrollView.contentSize.height < scrollView.frame.size.height) { + return scrollView.contentOffset.y >= -kPullToRefreshViewTriggerOffset; + } else { + return scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) - kPullToRefreshViewTriggerOffset; + } + } else { + return scrollView.contentOffset.y <= kPullToRefreshViewTriggerOffset; + } +} + +- (void)parkVisible { + if (isBottom) { + CGFloat extra = (scrollView.frame.size.height - scrollView.contentSize.height); + if (extra < 0.0f) extra = 0.0f; + scrollView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, -kPullToRefreshViewTriggerOffset + extra, 0.0f); + } else { + scrollView.contentInset = UIEdgeInsetsMake(-kPullToRefreshViewTriggerOffset, 0.0f, 0.0f, 0.0f); + } + } +} + +- (void)handleDragWhileLoading { + if ([self isScrolledToLimit] || [self isScrolledToVisible]) { + if (isBottom) { + CGFloat extra = (scrollView.frame.size.height - scrollView.contentSize.height); + if (extra < 0.0f) extra = 0.0f; + CGFloat visiblePortion = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.frame.size.height); + scrollView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, fminf(visiblePortion, -kPullToRefreshViewTriggerOffset + extra), 0.0f); + } else { + scrollView.contentInset = UIEdgeInsetsMake(fminf(-scrollView.contentOffset.y, -kPullToRefreshViewTriggerOffset), 0.0f, 0.0f, 0.0f); + } + } +} + +- (void)updatePosition { + if (isBottom) { + CGFloat bottomOffset = (scrollView.contentSize.height < scrollView.bounds.size.height) ? scrollView.bounds.size.height : scrollView.contentSize.height; + self.frame = CGRectMake(0.0f, bottomOffset, scrollView.bounds.size.width, scrollView.bounds.size.height); + } +} + #pragma mark - #pragma mark UIScrollView @@ -184,26 +285,19 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N // if we were in a refresh state if (state == kPullToRefreshViewStateReady) { // but now we're in between the "trigger" offset and 0 - if (scrollView.contentOffset.y > kPullToRefreshViewTriggerOffset && scrollView.contentOffset.y < 0.0f) { + if ([self isScrolledToVisible]) { // reset to "pull me to refresh!" [self setState:kPullToRefreshViewStateNormal]; } } else if (state == kPullToRefreshViewStateNormal) { // if we're in a normal state and we're above the top of the scrollView and we pass the max - if (scrollView.contentOffset.y < kPullToRefreshViewTriggerOffset) { + if ([self isScrolledToLimit]) { // go to the ready state. [self setState:kPullToRefreshViewStateReady]; } } else if (state == kPullToRefreshViewStateLoading || state == kPullToRefreshViewStateProgrammaticRefresh) { // if the user scrolls the view down while we're loading, make sure the loading screen is visible if they scroll to the top: - - if (scrollView.contentOffset.y >= 0) { - // this lets the table headers float to the top - scrollView.contentInset = UIEdgeInsetsZero; - } else { - // but show loading if they go past the top of the tableview - scrollView.contentInset = UIEdgeInsetsMake(fminf(-scrollView.contentOffset.y, -kPullToRefreshViewTriggerOffset), 0, 0, 0); - } + [self handleDragWhileLoading]; } } else { if (state == kPullToRefreshViewStateReady) { @@ -213,15 +307,14 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N [UIView setAnimationDuration:kPullToRefreshViewAnimationDuration]; [self setState:kPullToRefreshViewStateLoading]; [UIView commitAnimations]; - - if ([delegate respondsToSelector:@selector(pullToRefreshViewShouldRefresh:)]) - [delegate pullToRefreshViewShouldRefresh:self]; } } // Fix for view moving laterally with webView self.frame = CGRectMake(scrollView.contentOffset.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height); - } + } else if ([keyPath isEqualToString:@"contentSize"]) { + [self updatePosition]; + } } #pragma mark - @@ -229,23 +322,39 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N - (void)containingViewDidUnload { [scrollView removeObserver:self forKeyPath:@"contentOffset"]; - [scrollView release]; + [scrollView removeObserver:self forKeyPath:@"contentSize"]; + +#if __has_feature(objc_arc) + SAFE_ARC_RELEASE(scrollView); +#else + [scrollView release]; +#endif scrollView = nil; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - if (scrollView != nil) { // probably leaking the scrollView + if (scrollView != nil) { NSLog(@"PullToRefreshView: Leaking a scrollView?"); +#if __has_feature(objc_arc) + SAFE_ARC_RELEASE(scrollView); +#else [scrollView release]; +#endif } - [arrowImage release]; +#if __has_feature(objc_arc) + SAFE_ARC_RELEASE(arrowImage); + SAFE_ARC_RELEASE(statusLabel); + SAFE_ARC_RELEASE(subtitleLabel); + SAFE_ARC_SUPER_DEALLOC(); +#else + [arrowImage release]; [statusLabel release]; [subtitleLabel release]; - - [super dealloc]; + [super dealloc]; +#endif } @end From 0426966e26f7d29811df905ab3c2cd78ec82b358 Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Sun, 24 Jun 2012 00:21:53 -0300 Subject: [PATCH 4/9] Make more readable --- README | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/README b/README index 30e1dc5..8176995 100644 --- a/README +++ b/README @@ -9,23 +9,30 @@ It is: To implement it: + +Setup: - add the five files (PullToRefreshView.{h,m}, ARCMacros.h, arrow.png and arrow@2x.png) to your project - add the Quartz framework to your project if you haven't done so yet - - #import "PullToRefreshView.h" - add QuartzCore to your project - - add an ivar: PullToRefreshView *pull; // or whatever you want to name it - - in loadView or viewDidLoad, add this (and be sure to release in dealloc/viewDidUnload, etc): + +in your .h: + - add #import "PullToRefreshView.h" + - add the instance variable: PullToRefreshView *pull; + +in your .m: + - add the following to loadView or viewDidLoad (and be sure to release in dealloc/viewDidUnload, etc): pull = [[PullToRefreshView alloc] initWithScrollView:]; [pull setDelegate:self]; [ addSubview:pull]; - in dealloc and viewDidUnload, add calls to: - [pull containingViewDidUnload]; - to unwind the view hierarchy. + [pull containingViewDidUnload]; - implement two delegate methods: - // called when the user pulls-to-refresh - - (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view; - // called when the date shown needs to be updated, optional - - (NSDate *)pullToRefreshViewLastUpdated:(PullToRefreshView *)view; - - call -finishedLoading on the PullToRefreshView when you finished loading (or got an error, etc) - - that's it! no need to forward on UIScrollView delegate methods or anything silly like that. + - (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view; // required + - (NSDate *)pullToRefreshViewLastUpdated:(PullToRefreshView *)view; // optional + - In pullToRefreshViewShouldRefresh: call the following when you have finished loading (or hit an error): + [pull finishedLoading]; + + + + From b951aba0d38d5d3a71db08682774f4bd5008e492 Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Sat, 23 Jun 2012 23:22:41 -0400 Subject: [PATCH 5/9] Add remaining files --- ARCMacros.h | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100755 ARCMacros.h diff --git a/ARCMacros.h b/ARCMacros.h new file mode 100755 index 0000000..b65aa98 --- /dev/null +++ b/ARCMacros.h @@ -0,0 +1,66 @@ +// +// ARCMacros.h +// InnerBand +// +// For an explanation of why these work, see: +// +// http://raptureinvenice.com/arc-support-without-branches/ +// +// Created by John Blanco on 1/28/12. +// Copyright (c) 2012 Rapture In Venice. All rights reserved. +// +// NOTE: __bridge_tranfer is not included here because releasing would be inconsistent. +// Avoid it unless you're using ARC exclusively or managing it with __has_feature(objc_arc). +// + +#if !defined(__clang__) || __clang_major__ < 3 + #ifndef __bridge + #define __bridge + #endif + + #ifndef __bridge_retain + #define __bridge_retain + #endif + + #ifndef __bridge_retained + #define __bridge_retained + #endif + + #ifndef __autoreleasing + #define __autoreleasing + #endif + + #ifndef __strong + #define __strong + #endif + + #ifndef __unsafe_unretained + #define __unsafe_unretained + #endif + + #ifndef __weak + #define __weak + #endif +#endif + +#if __has_feature(objc_arc) + #define SAFE_ARC_PROP_RETAIN strong + #define SAFE_ARC_RETAIN(x) (x) + #define SAFE_ARC_RELEASE(x) + #define SAFE_ARC_AUTORELEASE(x) (x) + #define SAFE_ARC_BLOCK_COPY(x) (x) + #define SAFE_ARC_BLOCK_RELEASE(x) + #define SAFE_ARC_SUPER_DEALLOC() + #define SAFE_ARC_AUTORELEASE_POOL_START() @autoreleasepool { + #define SAFE_ARC_AUTORELEASE_POOL_END() } +#else + #define SAFE_ARC_PROP_RETAIN retain + #define SAFE_ARC_RETAIN(x) ([(x) retain]) + #define SAFE_ARC_RELEASE(x) ([(x) release]) + #define SAFE_ARC_AUTORELEASE(x) ([(x) autorelease]) + #define SAFE_ARC_BLOCK_COPY(x) (Block_copy(x)) + #define SAFE_ARC_BLOCK_RELEASE(x) (Block_release(x)) + #define SAFE_ARC_SUPER_DEALLOC() ([super dealloc]) + #define SAFE_ARC_AUTORELEASE_POOL_START() NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + #define SAFE_ARC_AUTORELEASE_POOL_END() [pool release]; +#endif From 2571a785d9c07e826b970f955b4212662f60ef2d Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Sun, 24 Jun 2012 00:32:38 -0300 Subject: [PATCH 6/9] Fixed a small semantics bug --- PullToRefreshView.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PullToRefreshView.m b/PullToRefreshView.m index 6d15264..d3e623a 100644 --- a/PullToRefreshView.m +++ b/PullToRefreshView.m @@ -248,7 +248,8 @@ - (BOOL)isScrolledToLimit { - (void)parkVisible { if (isBottom) { CGFloat extra = (scrollView.frame.size.height - scrollView.contentSize.height); - if (extra < 0.0f) extra = 0.0f; + if (extra < 0.0f) { + extra = 0.0f; scrollView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, -kPullToRefreshViewTriggerOffset + extra, 0.0f); } else { scrollView.contentInset = UIEdgeInsetsMake(-kPullToRefreshViewTriggerOffset, 0.0f, 0.0f, 0.0f); From a561bbc79f395063b2179e31a166ea9748aa21c3 Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Sun, 24 Jun 2012 00:34:44 -0300 Subject: [PATCH 7/9] Fixed another semantics bug --- PullToRefreshView.m | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/PullToRefreshView.m b/PullToRefreshView.m index d3e623a..4c47299 100644 --- a/PullToRefreshView.m +++ b/PullToRefreshView.m @@ -261,9 +261,11 @@ - (void)handleDragWhileLoading { if ([self isScrolledToLimit] || [self isScrolledToVisible]) { if (isBottom) { CGFloat extra = (scrollView.frame.size.height - scrollView.contentSize.height); - if (extra < 0.0f) extra = 0.0f; - CGFloat visiblePortion = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.frame.size.height); - scrollView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, fminf(visiblePortion, -kPullToRefreshViewTriggerOffset + extra), 0.0f); + if (extra < 0.0f) { + extra = 0.0f; + CGFloat visiblePortion = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.frame.size.height); + scrollView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, fminf(visiblePortion, -kPullToRefreshViewTriggerOffset + extra), 0.0f); + } } else { scrollView.contentInset = UIEdgeInsetsMake(fminf(-scrollView.contentOffset.y, -kPullToRefreshViewTriggerOffset), 0.0f, 0.0f, 0.0f); } From 44d68a5133ba90b8dbe2018c8aa062905689ef52 Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Fri, 29 Mar 2013 21:46:50 -0700 Subject: [PATCH 8/9] Reengineered --- .gitignore | 2 + ARCMacros.h | 66 ------- PullToRefreshView.h | 29 +-- PullToRefreshView.m | 422 +++++++++++++++----------------------------- README | 38 ---- README.markdown | 43 +++++ 6 files changed, 194 insertions(+), 406 deletions(-) create mode 100644 .gitignore delete mode 100755 ARCMacros.h delete mode 100644 README create mode 100644 README.markdown diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9bea433 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ + +.DS_Store diff --git a/ARCMacros.h b/ARCMacros.h deleted file mode 100755 index b65aa98..0000000 --- a/ARCMacros.h +++ /dev/null @@ -1,66 +0,0 @@ -// -// ARCMacros.h -// InnerBand -// -// For an explanation of why these work, see: -// -// http://raptureinvenice.com/arc-support-without-branches/ -// -// Created by John Blanco on 1/28/12. -// Copyright (c) 2012 Rapture In Venice. All rights reserved. -// -// NOTE: __bridge_tranfer is not included here because releasing would be inconsistent. -// Avoid it unless you're using ARC exclusively or managing it with __has_feature(objc_arc). -// - -#if !defined(__clang__) || __clang_major__ < 3 - #ifndef __bridge - #define __bridge - #endif - - #ifndef __bridge_retain - #define __bridge_retain - #endif - - #ifndef __bridge_retained - #define __bridge_retained - #endif - - #ifndef __autoreleasing - #define __autoreleasing - #endif - - #ifndef __strong - #define __strong - #endif - - #ifndef __unsafe_unretained - #define __unsafe_unretained - #endif - - #ifndef __weak - #define __weak - #endif -#endif - -#if __has_feature(objc_arc) - #define SAFE_ARC_PROP_RETAIN strong - #define SAFE_ARC_RETAIN(x) (x) - #define SAFE_ARC_RELEASE(x) - #define SAFE_ARC_AUTORELEASE(x) (x) - #define SAFE_ARC_BLOCK_COPY(x) (x) - #define SAFE_ARC_BLOCK_RELEASE(x) - #define SAFE_ARC_SUPER_DEALLOC() - #define SAFE_ARC_AUTORELEASE_POOL_START() @autoreleasepool { - #define SAFE_ARC_AUTORELEASE_POOL_END() } -#else - #define SAFE_ARC_PROP_RETAIN retain - #define SAFE_ARC_RETAIN(x) ([(x) retain]) - #define SAFE_ARC_RELEASE(x) ([(x) release]) - #define SAFE_ARC_AUTORELEASE(x) ([(x) autorelease]) - #define SAFE_ARC_BLOCK_COPY(x) (Block_copy(x)) - #define SAFE_ARC_BLOCK_RELEASE(x) (Block_release(x)) - #define SAFE_ARC_SUPER_DEALLOC() ([super dealloc]) - #define SAFE_ARC_AUTORELEASE_POOL_START() NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - #define SAFE_ARC_AUTORELEASE_POOL_END() [pool release]; -#endif diff --git a/PullToRefreshView.h b/PullToRefreshView.h index 94a729a..130b4f5 100644 --- a/PullToRefreshView.h +++ b/PullToRefreshView.h @@ -28,43 +28,28 @@ #import #import -#import "ARCMacros.h" typedef enum { - kPullToRefreshViewStateUninitialized = 0, - kPullToRefreshViewStateNormal, - kPullToRefreshViewStateReady, - kPullToRefreshViewStateLoading, - kPullToRefreshViewStateProgrammaticRefresh, - kPullToRefreshViewStateOffline + PullToRefreshViewStateUninitialized = 0, + PullToRefreshViewStateNormal, + PullToRefreshViewStateReady, + PullToRefreshViewStateLoading, + PullToRefreshViewStateProgrammaticRefresh, + PullToRefreshViewStateOffline } PullToRefreshViewState; @protocol PullToRefreshViewDelegate; -@interface PullToRefreshView : UIView { - __unsafe_unretained id delegate; - UIScrollView *scrollView; - PullToRefreshViewState state; - BOOL isBottom; +@interface PullToRefreshView : UIView - UILabel *subtitleLabel; - UILabel *statusLabel; - CALayer *arrowImage; - CALayer *offlineImage; - UIActivityIndicatorView *activityView; -} - -@property (nonatomic, readonly) UIScrollView *scrollView; @property (nonatomic, assign) id delegate; - (void)refreshLastUpdatedDate; - (id)initWithScrollView:(UIScrollView *)scrollView; -- (id)initWithScrollView:(UIScrollView *)scrollView atBottom:(BOOL)bottom; - (void)finishedLoading; - (void)beginLoading; - (void)setStatusLabelText:(NSString *)text; -- (void)containingViewDidUnload; @end diff --git a/PullToRefreshView.m b/PullToRefreshView.m index 4c47299..71325cd 100644 --- a/PullToRefreshView.m +++ b/PullToRefreshView.m @@ -35,329 +35,191 @@ #define kPullToRefreshViewAnimationDuration 0.18f #define kPullToRefreshViewTriggerOffset -65.0f -@interface PullToRefreshView (Private) +@interface PullToRefreshView () -@property (nonatomic, assign) PullToRefreshViewState state; +@property (nonatomic, retain) UILabel *statusLabel; +@property (nonatomic, retain) UILabel *subtitleLabel; +@property (nonatomic, retain) UIActivityIndicatorView *activityView; +@property (nonatomic, retain) UIScrollView *scrollView; +@property (nonatomic, retain) CALayer *offlineImage; +@property (nonatomic, retain) CALayer *arrowImage; -- (BOOL)isScrolledToVisible; -- (BOOL)isScrolledToLimit; -- (void)parkVisible; -- (void)handleDragWhileLoading; -- (void)updatePosition; +@property (nonatomic, retain) NSDateFormatter *dateFormatter; + +@property (nonatomic, assign, setter=setState:) PullToRefreshViewState state; @end @implementation PullToRefreshView -@synthesize delegate, scrollView; -- (void)showActivity:(BOOL)show animated:(BOOL)animated { - if (show) [activityView startAnimating]; - else [activityView stopAnimating]; +@synthesize statusLabel, subtitleLabel, activityView, scrollView, offlineImage, arrowImage, dateFormatter, state; + +- (void)setStatusLabelText:(NSString *)text { + [self.subtitleLabel setText:text]; +} + +- (void)beginLoading { + [self setState:PullToRefreshViewStateProgrammaticRefresh]; +} + +- (void)refreshLastUpdatedDate { + NSDate *date = [NSDate date]; + + if ([self.delegate respondsToSelector:@selector(pullToRefreshViewLastUpdated:)]) { + date = [self.delegate pullToRefreshViewLastUpdated:self]; + } + + self.subtitleLabel.text = [NSString stringWithFormat:@"Last Updated: %@", [self.dateFormatter stringFromDate:date]]; +} +- (void)showActivity:(BOOL)shouldShow animated:(BOOL)animated { + if (shouldShow) { + [self.activityView startAnimating]; + } else { + [self.activityView stopAnimating]; + } + [UIView beginAnimations:nil context:nil]; - [UIView setAnimationDuration:(animated ? kPullToRefreshViewAnimationDuration : 0.0)]; - arrowImage.opacity = (show ? 0.0 : 1.0); + [UIView setAnimationDuration:(animated ? 0.1f : 0.0)]; + self.arrowImage.opacity = (shouldShow ? 0.0 : 1.0); [UIView commitAnimations]; } - (void)setImageFlipped:(BOOL)flipped { [UIView beginAnimations:nil context:NULL]; - [UIView setAnimationDuration:kPullToRefreshViewAnimationDuration]; - arrowImage.transform = (flipped ^ isBottom ? CATransform3DMakeRotation(M_PI * 2, 0.0f, 0.0f, 1.0f) : CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f)); + [UIView setAnimationDuration:0.1f]; + self.arrowImage.transform = (flipped ? CATransform3DMakeRotation(M_PI * 2, 0.0f, 0.0f, 1.0f) : CATransform3DMakeRotation(M_PI, 0.0f, 0.0f, 1.0f)); [UIView commitAnimations]; } -- (id)initWithScrollView:(UIScrollView *)scroll atBottom:(BOOL)bottom { - CGFloat bottomOffset = (scroll.contentSize.height < scroll.bounds.size.height) ? scroll.bounds.size.height : scroll.contentSize.height; - CGFloat offset = bottom ? bottomOffset : 0.0f - scroll.bounds.size.height; - CGRect frame = CGRectMake(0.0f, offset, scroll.bounds.size.width, scroll.bounds.size.height); - - if ((self = [super initWithFrame:frame])) { - CGFloat visibleBottom = bottom ? -kPullToRefreshViewTriggerOffset : self.frame.size.height; - isBottom = bottom; +- (id)initWithScrollView:(UIScrollView *)scroll { + CGRect frame = CGRectMake(0.0f, 0.0f - scroll.bounds.size.height, scroll.bounds.size.width, scroll.bounds.size.height); + + if (self = [super initWithFrame:frame]) { -#if __has_feature(objc_arc) - // ARC is On - scrollView = SAFE_ARC_RETAIN(scroll); -#else - // ARC is Off - scrollView = [scroll retain]; -#endif + self.dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; + [self.dateFormatter setAMSymbol:@"AM"]; + [self.dateFormatter setPMSymbol:@"PM"]; + [self.dateFormatter setDateFormat:@"MM/dd/yy hh:mm a"]; + + self.scrollView = scroll; + [self.scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:NULL]; - - [scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:NULL]; - self.autoresizingMask = UIViewAutoresizingFlexibleWidth; - self.backgroundColor = kPullToRefreshViewBackgroundColor; - - statusLabel = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, visibleBottom - 48.0f, self.frame.size.width, 20.0f)]; - subtitleLabel.frame = CGRectMake(0.0f, visibleBottom - 30.0f, self.frame.size.width, 20.0f); - subtitleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - subtitleLabel.font = [UIFont systemFontOfSize:12.0f]; - subtitleLabel.textColor = kPullToRefreshViewSubtitleColor; - subtitleLabel.shadowColor = [UIColor colorWithWhite:0.9f alpha:1.0f]; - subtitleLabel.shadowOffset = CGSizeMake(0.0f, 1.0f); - subtitleLabel.backgroundColor = [UIColor clearColor]; - subtitleLabel.textAlignment = UITextAlignmentCenter; - [self addSubview:subtitleLabel]; - - statusLabel = [[UILabel alloc] init]; - statusLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; - statusLabel.font = [UIFont systemFontOfSize:12.f]; - statusLabel.textColor = kPullToRefreshViewTitleColor; - statusLabel.shadowColor = [UIColor colorWithWhite:0.9f alpha:1.0f]; - statusLabel.shadowOffset = CGSizeMake(0.0f, 1.0f); - statusLabel.backgroundColor = [UIColor clearColor]; - statusLabel.textAlignment = UITextAlignmentCenter; - [self addSubview:statusLabel]; - - arrowImage = [[CALayer alloc] init]; - UIImage *arrow = [UIImage imageNamed:@"arrow"]; - arrowImage.contents = (id)arrow.CGImage; - arrowImage.frame = CGRectMake(25.0f, visibleBottom + kPullToRefreshViewTriggerOffset + 5.0f, arrow.size.width, arrow.size.height); - arrowImage.contentsGravity = kCAGravityResizeAspect; - [self setImageFlipped:NO]; - - activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray]; - activityView.frame = CGRectMake(30.0f, visibleBottom - 38.0f, 20.0f, 20.0f); - [self addSubview:activityView]; - -#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 40000 - if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { - arrowImage.contentsScale = [[UIScreen mainScreen] scale]; - } -#endif - - [self.layer addSublayer:arrowImage]; - [self setState:kPullToRefreshViewStateNormal]; - } - - return self; -} - -- (id)initWithScrollView:(UIScrollView *)scroll { - return [self initWithScrollView:scroll atBottom:NO]; -} - - -#pragma mark - -#pragma mark Setters - -- (void)setStatusLabelText:(NSString *)text { - [statusLabel setText:text]; -} - -- (void)refreshLastUpdatedDate { - NSDate *date = [NSDate date]; - - if ([delegate respondsToSelector:@selector(pullToRefreshViewLastUpdated:)]) { - date = [delegate pullToRefreshViewLastUpdated:self]; + self.backgroundColor = [UIColor clearColor]; + + self.statusLabel = [[[UILabel alloc]initWithFrame:CGRectMake(0.0f, frame.size.height - 38.0f, self.frame.size.width, 20.0f)]autorelease]; + self.statusLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + + if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { + self.statusLabel.font = [UIFont boldSystemFontOfSize:18.0f]; + } else { + self.statusLabel.font = [UIFont boldSystemFontOfSize:13.0f]; + } + + self.statusLabel.textColor = [UIColor whiteColor]; + self.statusLabel.shadowColor = [UIColor darkGrayColor]; + self.statusLabel.shadowOffset = CGSizeMake(-1, -1); + self.statusLabel.backgroundColor = [UIColor clearColor]; + self.statusLabel.textAlignment = UITextAlignmentCenter; + [self addSubview:self.statusLabel]; + + self.arrowImage = [[[CALayer alloc]init]autorelease]; + self.arrowImage.frame = CGRectMake(25.0f, frame.size.height - 60.0f, 30.7f, 52.0f); // 30.7f was 24.0f + self.arrowImage.contentsGravity = kCAGravityCenter; + self.arrowImage.contentsScale = 2; // scale down the image regardless of retina. The image is by default the retina size. + self.arrowImage.contents = (id)[UIImage imageNamed:@"arrow"].CGImage; + [self.layer addSublayer:self.arrowImage]; + + self.activityView = [[[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]autorelease]; + self.activityView.frame = CGRectMake(30.0f, frame.size.height - 38.0f, 20.0f, 20.0f); + [self addSubview:self.activityView]; + + [self setState:PullToRefreshViewStateNormal]; } - - NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; - [formatter setAMSymbol:@"AM"]; - [formatter setPMSymbol:@"PM"]; - [formatter setDateFormat:@"MM/dd/yy hh:mm a"]; - subtitleLabel.text = [NSString stringWithFormat:@"Last Updated: %@", [formatter stringFromDate:date]]; -#if __has_feature(objc_arc) - // ARC is On - SAFE_ARC_RELEASE(formatter); -#else - // ARC is Off - [formatter release]; -#endif + return self; } -- (void)beginLoading { - [self setState:kPullToRefreshViewStateProgrammaticRefresh]; -} - -- (void)finishedLoading { - if (state == kPullToRefreshViewStateLoading || state == kPullToRefreshViewStateProgrammaticRefresh) { - [self refreshLastUpdatedDate]; - [UIView beginAnimations:nil context:NULL]; - [UIView setAnimationDuration:0.3f]; - [self setState:kPullToRefreshViewStateNormal]; - [UIView commitAnimations]; +- (void)setState:(PullToRefreshViewState)aState { + + if (aState == self.state) { + return; } -} - -- (void)setState:(PullToRefreshViewState)state_ { - if (state_ == state) return; - state = state_; - - switch (state) { - case kPullToRefreshViewStateReady: - statusLabel.text = @"Release to refresh…"; - [self showActivity:NO animated:NO]; + + state = aState; + + switch (self.state) { + case PullToRefreshViewStateReady: + self.statusLabel.text = @"Release to refresh..."; + [self showActivity:NO animated:NO]; [self setImageFlipped:YES]; - scrollView.contentInset = UIEdgeInsetsZero; - break; - case kPullToRefreshViewStateNormal: - statusLabel.text = [NSString stringWithFormat:@"Pull %@ to refresh...", isBottom ? @"up" : @"down"]; - [self showActivity:NO animated:NO]; + self.scrollView.contentInset = UIEdgeInsetsZero; + break; + + case PullToRefreshViewStateNormal: + self.statusLabel.text = @"Pull down to refresh..."; + [self showActivity:NO animated:NO]; [self setImageFlipped:NO]; - scrollView.contentInset = UIEdgeInsetsZero; - break; - case kPullToRefreshViewStateLoading: - case kPullToRefreshViewStateProgrammaticRefresh: - statusLabel.text = @"Loading…"; - [self showActivity:YES animated:YES]; + self.scrollView.contentInset = UIEdgeInsetsZero; + break; + + case PullToRefreshViewStateLoading: + self.statusLabel.text = @"Loading..."; + [self showActivity:YES animated:YES]; [self setImageFlipped:NO]; - if ([delegate respondsToSelector:@selector(pullToRefreshViewShouldRefresh:)]) { - [delegate pullToRefreshViewShouldRefresh:self]; - } - //scrollView.contentInset = UIEdgeInsetsMake(fminf(-scrollView.contentOffset.y, -kPullToRefreshViewTriggerOffset), 0, 0, 0); - [self parkVisible]; - break; + self.scrollView.contentInset = UIEdgeInsetsMake(60.0f, 0.0f, 0.0f, 0.0f); + break; + default: - break; + break; } - - [self setNeedsLayout]; } -- (BOOL)isScrolledToVisible { - if (isBottom) { - BOOL scrolledBelowContent; - if (scrollView.contentSize.height < scrollView.frame.size.height) { - scrolledBelowContent = scrollView.contentOffset.y > 0.0f; - } else { - scrolledBelowContent = scrollView.contentOffset.y > (scrollView.contentSize.height - scrollView.frame.size.height); - } - return scrolledBelowContent && ![self isScrolledToLimit]; - } else { - BOOL scrolledAboveContent = scrollView.contentOffset.y < 0.0f; - return scrolledAboveContent && ![self isScrolledToLimit]; - } -} - -- (BOOL)isScrolledToLimit { - if (isBottom) { - if (scrollView.contentSize.height < scrollView.frame.size.height) { - return scrollView.contentOffset.y >= -kPullToRefreshViewTriggerOffset; - } else { - return scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height) - kPullToRefreshViewTriggerOffset; - } - } else { - return scrollView.contentOffset.y <= kPullToRefreshViewTriggerOffset; - } -} - -- (void)parkVisible { - if (isBottom) { - CGFloat extra = (scrollView.frame.size.height - scrollView.contentSize.height); - if (extra < 0.0f) { - extra = 0.0f; - scrollView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, -kPullToRefreshViewTriggerOffset + extra, 0.0f); - } else { - scrollView.contentInset = UIEdgeInsetsMake(-kPullToRefreshViewTriggerOffset, 0.0f, 0.0f, 0.0f); - } - } -} - -- (void)handleDragWhileLoading { - if ([self isScrolledToLimit] || [self isScrolledToVisible]) { - if (isBottom) { - CGFloat extra = (scrollView.frame.size.height - scrollView.contentSize.height); - if (extra < 0.0f) { - extra = 0.0f; - CGFloat visiblePortion = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.frame.size.height); - scrollView.contentInset = UIEdgeInsetsMake(0.0f, 0.0f, fminf(visiblePortion, -kPullToRefreshViewTriggerOffset + extra), 0.0f); +- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { + if ([keyPath isEqualToString:@"contentOffset"]) { + if (self.scrollView.isDragging) { + if (self.state == PullToRefreshViewStateReady) { + if (self.scrollView.contentOffset.y > -65.0f && self.scrollView.contentOffset.y < 0.0f) + [self setState:PullToRefreshViewStateNormal]; + } else if (self.state == PullToRefreshViewStateNormal) { + if (self.scrollView.contentOffset.y < -65.0f) + [self setState:PullToRefreshViewStateReady]; + } else if (self.state == PullToRefreshViewStateLoading) { + if (self.scrollView.contentOffset.y >= 0) { + self.scrollView.contentInset = UIEdgeInsetsZero; + } else { + self.scrollView.contentInset = UIEdgeInsetsMake(MIN(-self.scrollView.contentOffset.y, 60.0f), 0, 0, 0); + } } } else { - scrollView.contentInset = UIEdgeInsetsMake(fminf(-scrollView.contentOffset.y, -kPullToRefreshViewTriggerOffset), 0.0f, 0.0f, 0.0f); + if (self.state == PullToRefreshViewStateReady) { + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.2f]; + [self setState:PullToRefreshViewStateLoading]; + [UIView commitAnimations]; + + if ([self.delegate respondsToSelector:@selector(pullToRefreshViewShouldRefresh:)]) + [self.delegate pullToRefreshViewShouldRefresh:self]; + } } } } -- (void)updatePosition { - if (isBottom) { - CGFloat bottomOffset = (scrollView.contentSize.height < scrollView.bounds.size.height) ? scrollView.bounds.size.height : scrollView.contentSize.height; - self.frame = CGRectMake(0.0f, bottomOffset, scrollView.bounds.size.width, scrollView.bounds.size.height); - } -} - -#pragma mark - -#pragma mark UIScrollView - -- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { - if ([keyPath isEqualToString:@"contentOffset"]) { - if (scrollView.isDragging) { - // if we were in a refresh state - if (state == kPullToRefreshViewStateReady) { - // but now we're in between the "trigger" offset and 0 - if ([self isScrolledToVisible]) { - // reset to "pull me to refresh!" - [self setState:kPullToRefreshViewStateNormal]; - } - } else if (state == kPullToRefreshViewStateNormal) { - // if we're in a normal state and we're above the top of the scrollView and we pass the max - if ([self isScrolledToLimit]) { - // go to the ready state. - [self setState:kPullToRefreshViewStateReady]; - } - } else if (state == kPullToRefreshViewStateLoading || state == kPullToRefreshViewStateProgrammaticRefresh) { - // if the user scrolls the view down while we're loading, make sure the loading screen is visible if they scroll to the top: - [self handleDragWhileLoading]; - } - } else { - if (state == kPullToRefreshViewStateReady) { - // if we're in state ready and a drag stops, then transition to loading. - - [UIView beginAnimations:nil context:NULL]; - [UIView setAnimationDuration:kPullToRefreshViewAnimationDuration]; - [self setState:kPullToRefreshViewStateLoading]; - [UIView commitAnimations]; - } - } - - // Fix for view moving laterally with webView - self.frame = CGRectMake(scrollView.contentOffset.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height); - } else if ([keyPath isEqualToString:@"contentSize"]) { - [self updatePosition]; +- (void)finishedLoading { + if (self.state == PullToRefreshViewStateLoading) { + [UIView beginAnimations:nil context:NULL]; + [UIView setAnimationDuration:0.3f]; + [self setState:PullToRefreshViewStateNormal]; + [UIView commitAnimations]; } } -#pragma mark - -#pragma mark Memory management - -- (void)containingViewDidUnload { - [scrollView removeObserver:self forKeyPath:@"contentOffset"]; - [scrollView removeObserver:self forKeyPath:@"contentSize"]; - -#if __has_feature(objc_arc) - SAFE_ARC_RELEASE(scrollView); -#else - [scrollView release]; -#endif - scrollView = nil; -} - - (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - if (scrollView != nil) { - NSLog(@"PullToRefreshView: Leaking a scrollView?"); -#if __has_feature(objc_arc) - SAFE_ARC_RELEASE(scrollView); -#else - [scrollView release]; -#endif - } - -#if __has_feature(objc_arc) - SAFE_ARC_RELEASE(arrowImage); - SAFE_ARC_RELEASE(statusLabel); - SAFE_ARC_RELEASE(subtitleLabel); - SAFE_ARC_SUPER_DEALLOC(); -#else - [arrowImage release]; - [statusLabel release]; - [subtitleLabel release]; + [self.scrollView removeObserver:self forKeyPath:@"contentOffset"]; + [self setDelegate:nil]; + [self setDateFormatter:nil]; [super dealloc]; -#endif } @end diff --git a/README b/README deleted file mode 100644 index 8176995..0000000 --- a/README +++ /dev/null @@ -1,38 +0,0 @@ -PullToRefreshView - -It is: - - a pull-to-refresh implementation - - very easy to implement - - doesn't suck - - Supports ARC and Non-ARC - - Add to bottom or top - - -To implement it: - -Setup: - - add the five files (PullToRefreshView.{h,m}, ARCMacros.h, arrow.png and arrow@2x.png) to your project - - add the Quartz framework to your project if you haven't done so yet - - add QuartzCore to your project - -in your .h: - - add #import "PullToRefreshView.h" - - add the instance variable: PullToRefreshView *pull; - -in your .m: - - add the following to loadView or viewDidLoad (and be sure to release in dealloc/viewDidUnload, etc): - pull = [[PullToRefreshView alloc] initWithScrollView:]; - [pull setDelegate:self]; - [ addSubview:pull]; - - in dealloc and viewDidUnload, add calls to: - [pull containingViewDidUnload]; - - implement two delegate methods: - - (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view; // required - - (NSDate *)pullToRefreshViewLastUpdated:(PullToRefreshView *)view; // optional - - In pullToRefreshViewShouldRefresh: call the following when you have finished loading (or hit an error): - [pull finishedLoading]; - - - - - diff --git a/README.markdown b/README.markdown new file mode 100644 index 0000000..d8c7b38 --- /dev/null +++ b/README.markdown @@ -0,0 +1,43 @@ +PullToRefreshView +== +Created by @chpwn (Grant Paul)
+Reengineered by @natesymer (Nathaniel Symer) + +**It is:** + + - a pull-to-refresh implementation + - very easy to implement + - doesn't suck + +**To implement it:** + +*Setup:* + + - Add the four files (PullToRefreshView.{h,m}, arrow.png and arrow@2x.png) to your project + - Link against QuartzCore.framework + - `#import "PullToRefreshView.h"` + +**Example implementation:** + + - (void)viewDidLoad { + ... viewDidLoad by you ... + PullToRefreshView *pull = [[PullToRefreshView alloc]initWithScrollView:aScrollView]; + [pull setDelegate:self]; + [aScrollView addSubview:pull]; + [pull release]; + } + + - (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)aPull { + ... your code ... + [aPull finishedLoading]; // Use common sense with placement of this line + } + + - (NSDate *)pullToRefreshViewLastUpdated:(PullToRefreshView *)view { + ... your code ... + return someDate; + } + + + + + From 4b8d35a639155a2d968a5eecfef9f5275b97b037 Mon Sep 17 00:00:00 2001 From: Nathaniel Symer Date: Fri, 29 Mar 2013 21:47:40 -0700 Subject: [PATCH 9/9] Removed outdated build script --- testbuild.sh | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 testbuild.sh diff --git a/testbuild.sh b/testbuild.sh deleted file mode 100644 index 79b6e4f..0000000 --- a/testbuild.sh +++ /dev/null @@ -1,3 +0,0 @@ -# this will fail with _main undefined, but should work otherwise -/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/clang++ -isysroot /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS5.0.sdk PullToRefreshView.m -arch armv7 -framework Foundation -framework UIKit -miphoneos-version-min=4.0 -framework QuartzCore -