Skip to content

Commit

Permalink
Use weak properties
Browse files Browse the repository at this point in the history
This change makes SSPullToRefresh require iOS 5.0. I'd love to find a work around so the user doesn't have to call `removeFromSuperview` to prevent the KVO leak. Here's my SO question <http://stackoverflow.com/questions/10793206/how-do-you-remove-kvo-from-a-weak-property>
  • Loading branch information
soffes committed May 29, 2012
1 parent fba57e0 commit 513f1d0
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 28 deletions.
12 changes: 12 additions & 0 deletions Readme.markdown
Expand Up @@ -2,6 +2,8 @@

There are dozens of pull to refresh views. I've never found one I'm happy with. I always end up customizing one, so I decided to write one that's highly customizable. You can just write you view and forget about the actual pull to refresh details.

SSPullToRefresh requires iOS 5.0 or greater and uses ARC. You can use it in non-ARC projects though. See [Adding To Your Project](https://github.com/samsoffes/sspulltorefresh#adding-to-your-project).

If you're using SSPullToRefresh in your application, add it to [the list](https://github.com/samsoffes/sspulltorefresh/wiki/Applications).


Expand All @@ -13,6 +15,11 @@ If you're using SSPullToRefresh in your application, add it to [the list](https:
self.pullToRefreshView = [[SSPullToRefreshView alloc] initWithScrollView:self.tableView delegate:self];
}

- (void)viewUnload {
[super viewUnload];
self.pullToRefreshView.delegate = nil;
[self.pullToRefreshView removeFromSuperview];
}

- (void)refresh {
[self.pullToRefreshView startLoading];
Expand All @@ -23,6 +30,11 @@ If you're using SSPullToRefresh in your application, add it to [the list](https:
- (void)pullToRefreshViewDidStartLoading:(SSPullToRefreshView *)view {
[self refresh];
}

- (void)dealloc {
self.pullToRefreshView.delegate = nil;
[self.pullToRefreshView removeFromSuperview];
}
```


Expand Down
14 changes: 11 additions & 3 deletions SSPullToRefreshView.h
Expand Up @@ -13,7 +13,11 @@
// [super viewDidLoad];
// self.pullToRefreshView = [[SSPullToRefreshView alloc] initWithScrollView:self.tableView delegate:self];
// }
//
//
// - (void)viewDidUnload {
// [super viewDidUnload];
// [self.pullToRefreshView removeFromSuperview];
// }
//
// - (void)refresh {
// [self.pullToRefreshView startLoading];
Expand All @@ -25,6 +29,10 @@
// [self refresh];
// }
//
// - (void)dealloc {
// [self.pullToRefreshView removeFromSuperview];
// }
//

#import <UIKit/UIKit.h>

Expand Down Expand Up @@ -84,15 +92,15 @@ typedef enum {
@see initWithScrollView:delegate:
*/
@property (nonatomic, assign, readonly) UIScrollView *scrollView;
@property (nonatomic, weak, readonly) UIScrollView *scrollView;

/**
The delegate is sent messages when the pull to refresh view starts loading. This is automatically set with `initWithScrollView:delegate:`.
@see initWithScrollView:delegate:
@see SSPullToRefreshViewDelegate
*/
@property (nonatomic, assign) id<SSPullToRefreshViewDelegate> delegate;
@property (nonatomic, weak) id<SSPullToRefreshViewDelegate> delegate;

/**
The state of the pull to refresh view.
Expand Down
56 changes: 31 additions & 25 deletions SSPullToRefreshView.m
Expand Up @@ -11,7 +11,7 @@

@interface SSPullToRefreshView ()
@property (nonatomic, assign, readwrite) SSPullToRefreshViewState state;
@property (nonatomic, assign, readwrite) UIScrollView *scrollView;
@property (nonatomic, weak, readwrite) UIScrollView *scrollView;
@property (nonatomic, assign, readwrite, getter = isExpanded) BOOL expanded;
- (void)_setContentInsetTop:(CGFloat)topInset;
- (void)_setState:(SSPullToRefreshViewState)state animated:(BOOL)animated expanded:(BOOL)expanded completion:(void (^)(void))completion;
Expand Down Expand Up @@ -43,13 +43,13 @@ - (void)setState:(SSPullToRefreshViewState)state {

// Update delegate
if (loading && _state != SSPullToRefreshViewStateLoading) {
if ([_delegate respondsToSelector:@selector(pullToRefreshViewDidFinishLoading:)]) {
[_delegate pullToRefreshViewDidFinishLoading:self];
if ([self.delegate respondsToSelector:@selector(pullToRefreshViewDidFinishLoading:)]) {
[self.delegate pullToRefreshViewDidFinishLoading:self];
}
} else if (!loading && _state == SSPullToRefreshViewStateLoading) {
[self _setPullProgress:1.0f];
if ([_delegate respondsToSelector:@selector(pullToRefreshViewDidStartLoading:)]) {
[_delegate pullToRefreshViewDidStartLoading:self];
if ([self.delegate respondsToSelector:@selector(pullToRefreshViewDidStartLoading:)]) {
[self.delegate pullToRefreshViewDidStartLoading:self];
}
}
}
Expand All @@ -62,16 +62,8 @@ - (void)setExpanded:(BOOL)expanded {


- (void)setScrollView:(UIScrollView *)scrollView {
void *context = (__bridge void *)self;
if ([_scrollView respondsToSelector:@selector(removeObserver:forKeyPath:context:)]) {
[_scrollView removeObserver:self forKeyPath:@"contentOffset" context:context];
} else if (_scrollView) {
[_scrollView removeObserver:self forKeyPath:@"contentOffset"];
}

_scrollView = scrollView;
_defaultContentInset = _scrollView.contentInset;
[_scrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:context];
_scrollView = scrollView;
_defaultContentInset = self.scrollView.contentInset;
}


Expand Down Expand Up @@ -104,9 +96,14 @@ - (void)setDefaultContentInset:(UIEdgeInsets)defaultContentInset {
#pragma mark - NSObject

- (void)dealloc {
[self removeObserver:self forKeyPath:@"scrollView.contentOffset" context:(__bridge void *)self];

self.scrollView = nil;
self.delegate = nil;

dispatch_semaphore_wait(_animationSemaphore, DISPATCH_TIME_FOREVER);
dispatch_release(_animationSemaphore);
_animationSemaphore = NULL;
}


Expand Down Expand Up @@ -148,6 +145,7 @@ - (id)initWithScrollView:(UIScrollView *)scrollView delegate:(id<SSPullToRefresh

// Add to scroll view
[self.scrollView addSubview:self];
[self addObserver:self forKeyPath:@"scrollView.contentOffset" options:NSKeyValueObservingOptionNew context:(__bridge void *)self];

// Semaphore is used to ensure only one animation plays at a time
_animationSemaphore = dispatch_semaphore_create(0);
Expand Down Expand Up @@ -190,8 +188,8 @@ - (void)finishLoading {

- (void)refreshLastUpdatedAt {
NSDate *date = nil;
if ([_delegate respondsToSelector:@selector(pullToRefreshViewLastUpdatedAt:)]) {
date = [_delegate pullToRefreshViewLastUpdatedAt:self];
if ([self.delegate respondsToSelector:@selector(pullToRefreshViewLastUpdatedAt:)]) {
date = [self.delegate pullToRefreshViewLastUpdatedAt:self];
} else {
date = [NSDate date];
}
Expand All @@ -215,12 +213,12 @@ - (void)_setContentInsetTop:(CGFloat)topInset {
inset.top += _topInset;

// Don't set it if that is already the current inset
if (UIEdgeInsetsEqualToEdgeInsets(_scrollView.contentInset, inset)) {
if (UIEdgeInsetsEqualToEdgeInsets(self.scrollView.contentInset, inset)) {
return;
}

// Update the content inset
_scrollView.contentInset = inset;
self.scrollView.contentInset = inset;
}


Expand Down Expand Up @@ -267,17 +265,25 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}

// We don't care about this notificaiton
if (object != _scrollView || ![keyPath isEqualToString:@"contentOffset"]) {
if (object != self || ![keyPath isEqualToString:@"scrollView.contentOffset"]) {
return;
}

// Get the offset out of the change notification
CGFloat y = [[change objectForKey:NSKeyValueChangeNewKey] CGPointValue].y;
id point = [change objectForKey:NSKeyValueChangeNewKey];

// Ensure the point is valid
if (!point || point == [NSNull null] || ![point respondsToSelector:@selector(CGPointValue)]) {
return;
}

// Retrieve the y offset from the point
CGFloat y = [point CGPointValue].y;

// Scroll view is dragging
if (_scrollView.isDragging) {
if (self.scrollView.isDragging) {
// Scroll view is ready
if (_state == SSPullToRefreshViewStateReady) {
// Dragged enough to refresh
Expand Down Expand Up @@ -313,8 +319,8 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
SSPullToRefreshViewState newState = SSPullToRefreshViewStateLoading;

// Ask the delegate if it's cool to start loading
if ([_delegate respondsToSelector:@selector(pullToRefreshViewShouldStartLoading:)]) {
if (![_delegate pullToRefreshViewShouldStartLoading:self]) {
if ([self.delegate respondsToSelector:@selector(pullToRefreshViewShouldStartLoading:)]) {
if (![self.delegate pullToRefreshViewShouldStartLoading:self]) {
// Animate back to normal since the delegate said no
newState = SSPullToRefreshViewStateNormal;
}
Expand Down

0 comments on commit 513f1d0

Please sign in to comment.