Files
RestKit/Code/UI/RKRefreshGestureRecognizer.m

230 lines
8.7 KiB
Objective-C

// RKRefreshGestureRecognizer.m
// RestKit
//
// Based on PHRefreshTriggerView by Pier-Olivier Thibault
// Adapted by Gregory S. Combs on 1/13/2012
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#if TARGET_OS_IPHONE
#import <QuartzCore/QuartzCore.h>
#import <UIKit/UIGestureRecognizerSubclass.h>
#import "RKRefreshGestureRecognizer.h"
NSString * const RKRefreshGestureAnimationKey = @"RKRefreshGestureAnimationKey";
NSString * const RKRefreshResetGestureAnimationKey = @"RKRefreshResetGestureAnimationKey";
static CGFloat const kFlipArrowAnimationTime = 0.18f;
static CGFloat const kDefaultTriggerViewHeight = 64.f;
@interface RKRefreshGestureRecognizer ()
- (CABasicAnimation *)triggeredAnimation;
- (CABasicAnimation *)idlingAnimation;
@property (nonatomic, retain, readwrite) RKRefreshTriggerView *triggerView;
@property (nonatomic, assign) BOOL isBoundToScrollView;
@property (nonatomic, retain) NSDateFormatter *dateFormatter;
@end
@implementation RKRefreshGestureRecognizer
#pragma mark - Synthesizers
@synthesize triggerView = _triggerView;
@synthesize refreshState = _refreshState;
@synthesize isBoundToScrollView = _isBoundToScrollView;
@synthesize dateFormatter = _dateFormatter;
#pragma mark - Life Cycle
- (id)initWithTarget:(id)target action:(SEL)action {
self = [super initWithTarget:target action:action];
if (self) {
_triggerView = [[RKRefreshTriggerView alloc] initWithFrame:CGRectZero];
_triggerView.titleLabel.text = NSLocalizedString(@"Pull down to refresh...", @"Pull down to refresh status");
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setDateStyle:NSDateFormatterShortStyle];
[_dateFormatter setTimeStyle:NSDateFormatterShortStyle];
[self addObserver:self forKeyPath:@"view" options:NSKeyValueObservingOptionNew context:nil];
}
return self;
}
- (void)dealloc {
[self removeObserver:self forKeyPath:@"view"];
if (self.triggerView)
[self.triggerView removeFromSuperview];
self.triggerView = nil;
[super dealloc];
}
#pragma mark - Utilities
- (void)refreshLastUpdatedDate {
SEL lastUpdatedSelector = @selector(pullToRefreshDataSourceLastUpdated:);
if (self.scrollView.delegate && [self.scrollView.delegate respondsToSelector:lastUpdatedSelector]) {
NSDate *date = [self.scrollView.delegate performSelector:lastUpdatedSelector withObject:self];
if (!date)
return;
NSString *lastUpdatedText = [NSString stringWithFormat:@"Last Updated: %@", [self.dateFormatter stringFromDate:date]];
self.triggerView.lastUpdatedLabel.text = lastUpdatedText;
} else {
self.triggerView.lastUpdatedLabel.text = nil;
}
}
- (void)setRefreshState:(RKRefreshState)refreshState {
if (refreshState == _refreshState)
return;
__block UIScrollView *bScrollView = self.scrollView;
switch (refreshState) {
case RKRefreshTriggered: {
if (![self.triggerView.arrowView.layer animationForKey:RKRefreshGestureAnimationKey])
[self.triggerView.arrowView.layer addAnimation:[self triggeredAnimation] forKey:RKRefreshGestureAnimationKey];
self.triggerView.titleLabel.text = NSLocalizedString(@"Release to refresh...", @"Release to refresh status");
}
break;
case RKRefreshIdle: {
if (_refreshState == RKRefreshLoading) {
[UIView animateWithDuration:0.2 animations:^{
bScrollView.contentInset = UIEdgeInsetsMake(0,
bScrollView.contentInset.left,
bScrollView.contentInset.bottom,
bScrollView.contentInset.right);
}];
[self.triggerView.arrowView.layer removeAllAnimations];
[self.triggerView.activityView removeFromSuperview];
[self.triggerView.activityView stopAnimating];
[self.triggerView addSubview:self.triggerView.arrowView];
} else if (_refreshState == RKRefreshTriggered) {
if ([self.triggerView.arrowView.layer animationForKey:RKRefreshGestureAnimationKey]) {
[self.triggerView.arrowView.layer addAnimation:[self idlingAnimation] forKey:RKRefreshResetGestureAnimationKey];
}
}
[self refreshLastUpdatedDate];
self.triggerView.titleLabel.text = NSLocalizedString(@"Pull down to refresh...", @"Pull down to refresh status");
}
break;
case RKRefreshLoading: {
[UIView animateWithDuration:0.2 animations:^{
bScrollView.contentInset = UIEdgeInsetsMake(kDefaultTriggerViewHeight,
bScrollView.contentInset.left,
bScrollView.contentInset.bottom,
bScrollView.contentInset.right);
}];
self.triggerView.titleLabel.text = NSLocalizedString(@"Loading...", @"Loading Status");
[self.triggerView.arrowView removeFromSuperview];
[self.triggerView addSubview:self.triggerView.activityView];
[self.triggerView.activityView startAnimating];
}
break;
}
_refreshState = refreshState;
}
- (CABasicAnimation *)triggeredAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.duration = kFlipArrowAnimationTime;
animation.toValue = [NSNumber numberWithDouble:M_PI];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
return animation;
}
- (CABasicAnimation *)idlingAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
animation.delegate = self;
animation.duration = kFlipArrowAnimationTime;
animation.toValue = [NSNumber numberWithDouble:0];
animation.removedOnCompletion = YES;
return animation;
}
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
[self.triggerView.arrowView.layer removeAllAnimations];
}
- (UIScrollView *)scrollView {
return (UIScrollView *)self.view;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
id obj = [object valueForKeyPath:keyPath];
if (NO == [obj isKindOfClass:[UIScrollView class]]) {
self.isBoundToScrollView = NO;
return;
}
self.isBoundToScrollView = YES;
self.triggerView.frame = CGRectMake(0, -kDefaultTriggerViewHeight, CGRectGetWidth(self.view.frame), kDefaultTriggerViewHeight);
[obj addSubview:self.triggerView];
}
#pragma mark UIGestureRecognizer
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer {
return NO;
}
- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer {
return NO;
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.isBoundToScrollView)
return;
if (self.state < UIGestureRecognizerStateBegan) {
self.state = UIGestureRecognizerStateBegan;
}
if (self.scrollView.contentOffset.y < -kDefaultTriggerViewHeight) {
self.refreshState = RKRefreshTriggered;
self.state = UIGestureRecognizerStateChanged;
} else if (self.state != UIGestureRecognizerStateRecognized) {
self.refreshState = RKRefreshIdle;
self.state = UIGestureRecognizerStateChanged;
}
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if (!self.isBoundToScrollView) {
self.state = UIGestureRecognizerStateFailed;
return;
}
if (self.refreshState == RKRefreshTriggered) {
self.refreshState = RKRefreshLoading;
self.state = UIGestureRecognizerStateRecognized;
return;
}
self.state = UIGestureRecognizerStateCancelled;
self.refreshState = RKRefreshIdle;
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateCancelled;
}
@end
#endif