Last active
December 15, 2015 04:59
-
-
Save paul-delange/5205515 to your computer and use it in GitHub Desktop.
Storyboard support for ISRefreshControl
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <UIKit/UIKit.h> | |
#import "UITableViewController+RefreshControl.h" | |
@interface ISRefreshControl : UIControl; | |
@property (nonatomic, readonly, getter=isRefreshing) BOOL refreshing; | |
@property (nonatomic, retain) UIColor *tintColor UI_APPEARANCE_SELECTOR; | |
@property (nonatomic, retain) NSAttributedString *attributedTitle UI_APPEARANCE_SELECTOR; | |
- (void)beginRefreshing; | |
- (void)endRefreshing; | |
@end | |
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000 | |
#define ISUIRefreshControl ISUIRefreshControl_ | |
@interface ISUIRefreshControl_ : ISRefreshControl @end | |
#else | |
#define ISRefreshControl UIRefreshControl | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "ISRefreshControl.h" | |
#import "ISGumView.h" | |
#import "ISUtility.h" | |
#import "UIActivityIndicatorView+ScaleAnimation.h" | |
#import <objc/runtime.h> | |
#import <QuartzCore/QuartzCore.h> | |
const CGFloat additionalTopInset = 50.f; | |
@interface ISRefreshControl () | |
@property (nonatomic) BOOL topInsetsEnabled; | |
@property (nonatomic) BOOL animating; | |
@property (nonatomic) BOOL refreshing; | |
@property (nonatomic) BOOL refreshed; | |
@property (strong, nonatomic) ISGumView *gumView; | |
@property (strong, nonatomic) UIActivityIndicatorView *indicatorView; | |
@property (readonly, nonatomic) UITableView *superTableView; | |
@end | |
@implementation ISRefreshControl | |
- (id) initWithCoder:(NSCoder *)aDecoder { | |
self = [super initWithCoder: aDecoder]; | |
if( self ) { | |
self.gumView = [[ISGumView alloc] init]; | |
[self addSubview:self.gumView]; | |
self.indicatorView = [[UIActivityIndicatorView alloc] init]; | |
self.indicatorView.hidesWhenStopped = YES; | |
self.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; | |
self.indicatorView.color = [UIColor lightGrayColor]; | |
[self.indicatorView.layer setValue:@.01f forKeyPath:@"transform.scale"]; | |
[self addSubview:self.indicatorView]; | |
[self addObserver:self | |
forKeyPath:@"tintColor" | |
options:NSKeyValueObservingOptionNew | |
context:NULL]; | |
UIColor *tintColor = [[ISRefreshControl appearance] tintColor]; | |
if (tintColor) { | |
self.tintColor = tintColor; | |
} | |
} | |
return self; | |
} | |
- (id)initWithFrame:(CGRect)frame | |
{ | |
self = [super initWithFrame:frame]; | |
if (self) { | |
self.gumView = [[ISGumView alloc] init]; | |
[self addSubview:self.gumView]; | |
self.indicatorView = [[UIActivityIndicatorView alloc] init]; | |
self.indicatorView.hidesWhenStopped = YES; | |
self.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; | |
self.indicatorView.color = [UIColor lightGrayColor]; | |
[self.indicatorView.layer setValue:@.01f forKeyPath:@"transform.scale"]; | |
[self addSubview:self.indicatorView]; | |
[self addObserver:self | |
forKeyPath:@"tintColor" | |
options:NSKeyValueObservingOptionNew | |
context:NULL]; | |
UIColor *tintColor = [[ISRefreshControl appearance] tintColor]; | |
if (tintColor) { | |
self.tintColor = tintColor; | |
} | |
} | |
return self; | |
} | |
- (id) init { | |
self = [super init]; | |
if( self ) { | |
self.gumView = [[ISGumView alloc] init]; | |
[self addSubview:self.gumView]; | |
self.indicatorView = [[UIActivityIndicatorView alloc] init]; | |
self.indicatorView.hidesWhenStopped = YES; | |
self.indicatorView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge; | |
self.indicatorView.color = [UIColor lightGrayColor]; | |
[self.indicatorView.layer setValue:@.01f forKeyPath:@"transform.scale"]; | |
[self addSubview:self.indicatorView]; | |
[self addObserver:self | |
forKeyPath:@"tintColor" | |
options:NSKeyValueObservingOptionNew | |
context:NULL]; | |
UIColor *tintColor = [[ISRefreshControl appearance] tintColor]; | |
if (tintColor) { | |
self.tintColor = tintColor; | |
} | |
[self reset]; | |
} | |
return self; | |
} | |
- (void)dealloc | |
{ | |
[self removeObserver:self forKeyPath:@"tintColor"]; | |
} | |
#pragma mark - view events | |
- (void)layoutSubviews | |
{ | |
CGFloat width = self.frame.size.width; | |
self.gumView.frame = CGRectMake(width/2.f-15, 25-15, 35, 90); | |
self.indicatorView.frame = CGRectMake(width/2.f-15, 25-15, 30, 30); | |
} | |
- (void)willMoveToSuperview:(UIView *)newSuperview | |
{ | |
if ([self.superview isKindOfClass:[UIScrollView class]]) { | |
[self.superview removeObserver:self forKeyPath:@"contentOffset"]; | |
} | |
} | |
- (void)didMoveToSuperview | |
{ | |
if ([self.superview isKindOfClass:[UIScrollView class]]) { | |
[self.superview addObserver:self forKeyPath:@"contentOffset" options:0 context:NULL]; | |
self.frame = CGRectMake(0, -50, self.superview.frame.size.width, 50); | |
self.autoresizingMask = UIViewAutoresizingFlexibleWidth; | |
[self setNeedsLayout]; | |
} | |
} | |
#pragma mark - KVO | |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context | |
{ | |
if (object == self.superview && [keyPath isEqualToString:@"contentOffset"]) { | |
UIScrollView *scrollView = (UIScrollView *)self.superview; | |
CGFloat offset = scrollView.contentOffset.y; | |
// reset refresh status | |
if (!self.refreshing && !self.animating && offset >= 0) { | |
[self reset]; | |
} | |
// send UIControlEvent | |
if (!self.refreshing && !self.refreshed && offset <= -115 && scrollView.isTracking) { | |
[self beginRefreshing]; | |
[self sendActionsForControlEvents:UIControlEventValueChanged]; | |
} | |
// stays top and send distance to gumView | |
if (offset < -50) { | |
self.frame = CGRectMake(0, offset, self.frame.size.width, self.frame.size.height); | |
if (!self.gumView.shrinking) { | |
self.gumView.distance = -offset-50; | |
} | |
} else { | |
self.frame = CGRectMake(0, -50, self.frame.size.width, self.frame.size.height); | |
if (!self.gumView.shrinking) { | |
self.gumView.distance = 0.f; | |
} | |
} | |
// topInset | |
if (!scrollView.isDragging && self.refreshing && !self.animating && !self.topInsetsEnabled) { | |
[self setTopInsetsEnabled:YES completion:nil]; | |
} | |
return; | |
} | |
if (object == self && [keyPath isEqualToString:@"tintColor"]) { | |
self.gumView.tintColor = self.tintColor; | |
self.indicatorView.color = self.tintColor; | |
return; | |
} | |
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; | |
} | |
#pragma mark - | |
- (void)beginRefreshing | |
{ | |
if (self.refreshing) { | |
return; | |
} | |
self.refreshing = YES; | |
[self.superview bringSubviewToFront:self]; | |
[self.indicatorView startAnimating]; | |
[self.indicatorView expandWithCompletion:nil]; | |
[self.gumView shrink]; | |
} | |
- (void)endRefreshing | |
{ | |
if (!self.refreshing) { | |
return; | |
} | |
self.refreshing = NO; | |
self.refreshed = YES; | |
[self.superview bringSubviewToFront:self]; | |
[self.indicatorView shrinkWithCompletion:^(BOOL finished) { | |
[self.indicatorView stopAnimating]; | |
}]; | |
if (self.topInsetsEnabled) { | |
__weak typeof(self) wself = self; | |
[self setTopInsetsEnabled:NO completion:^{ | |
UIScrollView *scrollView = (UIScrollView *)wself.superview; | |
if (!scrollView.isDragging) { | |
[wself reset]; | |
} | |
}]; | |
} | |
} | |
- (void)reset | |
{ | |
self.refreshing = NO; | |
self.refreshed = NO; | |
self.gumView.hidden = NO; | |
} | |
- (void)setTopInsetsEnabled:(BOOL)enabled completion:(void (^)(void))completion | |
{ | |
if (![self.superview isKindOfClass:[UIScrollView class]]) { | |
return; | |
} | |
if (self.topInsetsEnabled == enabled) { | |
return; | |
} | |
self.topInsetsEnabled = enabled; | |
UIScrollView *scrollView = (id)self.superview; | |
CGFloat diff = additionalTopInset * (enabled?1.f:-1.f); | |
__weak typeof(self) wself = self; | |
wself.animating = YES; | |
[UIView animateWithDuration:.3f | |
animations:^{ | |
scrollView.contentInset = UIEdgeInsetsMake(scrollView.contentInset.top + diff, | |
scrollView.contentInset.left, | |
scrollView.contentInset.bottom, | |
scrollView.contentInset.right); | |
} | |
completion:^(BOOL finished) { | |
self.animating = NO; | |
if (completion) { | |
completion(); | |
}; | |
}]; | |
} | |
@end | |
/////////////////////////////////////////////////////////////////////////////////////////// | |
#pragma mark - Runtime Additions to create UIRefreshControl | |
#import <objc/message.h> | |
#if __IPHONE_OS_VERSION_MIN_REQUIRED < 60000 | |
@implementation ISUIRefreshControl_ @end | |
__attribute__((constructor)) static void ODCreateUIRefreshControlClasses(void) { | |
@autoreleasepool { | |
#pragma clang diagnostic push | |
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | |
if ([UIRefreshControl class]) class_setSuperclass([ISUIRefreshControl_ class], [UIRefreshControl class]); | |
else objc_registerClassPair(objc_allocateClassPair([ISRefreshControl class], "UIRefreshControl", 0)); | |
#pragma clang diagnostic pop | |
objc_registerClassPair(objc_allocateClassPair([ISUIRefreshControl_ class], "ISUIRefreshControl", 0)); | |
} | |
} | |
#endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "UITableViewController+RefreshControl.h" | |
#import "ISRefreshControl.h" | |
#import "ISUtility.h" | |
#import <objc/runtime.h> | |
@implementation UITableViewController (RefreshControl) | |
+ (void)load | |
{ | |
@autoreleasepool { | |
if ([[[UIDevice currentDevice] systemVersion] hasPrefix:@"5"]) { | |
IMP getter = class_getMethodImplementation(self, @selector(refreshControl)); | |
class_addMethod(self, @selector(refreshControl), getter, "@v:"); | |
IMP setter = class_getMethodImplementation(self, @selector(setRefreshControl:)); | |
class_addMethod(self, @selector(setRefreshControl:), setter, "v@:@"); | |
//SwizzleMethod([self class], @selector(initWithCoder:), @selector(initIOS5WithCoder:)); | |
SwizzleMethod([self class], @selector(refreshControl), @selector(iOS5_refreshControl)); | |
SwizzleMethod([self class], @selector(setRefreshControl:), @selector(iOS5_setRefreshControl:)); | |
SwizzleMethod([self class], @selector(viewDidLoad), @selector(iOS5_viewDidLoad)); | |
} | |
} | |
} | |
#pragma mark - | |
- (void)iOS5_viewDidLoad | |
{ | |
[super viewDidLoad]; | |
if (self.refreshControl) { | |
[self.view addSubview:self.refreshControl]; | |
} | |
} | |
- (ISRefreshControl *)iOS5_refreshControl | |
{ | |
return objc_getAssociatedObject(self.tableView, @"iOS5RefreshControl"); | |
} | |
- (void)iOS5_setRefreshControl:(ISRefreshControl *)refreshControl | |
{ | |
if (self.isViewLoaded) { | |
ISRefreshControl *oldRefreshControl = objc_getAssociatedObject(self, @"iOS5RefreshControl"); | |
[oldRefreshControl removeFromSuperview]; | |
[self.view addSubview:refreshControl]; | |
} | |
objc_setAssociatedObject(self.tableView, @"iOS5RefreshControl", refreshControl, OBJC_ASSOCIATION_RETAIN); | |
} | |
@end | |
@implementation UITableView (RefreshControl) | |
+ (void) load { | |
@autoreleasepool { | |
if ([[[UIDevice currentDevice] systemVersion] hasPrefix:@"5"]) { | |
SwizzleMethod([self class], @selector(initWithCoder:), @selector(initIOS5WithCoder:)); | |
} | |
} | |
} | |
- (id) initIOS5WithCoder:(NSCoder *)aDecoder { | |
self = [self initIOS5WithCoder: aDecoder]; | |
if( self ) { | |
UIRefreshControl* refreshControl = [aDecoder decodeObjectForKey: @"UIRefreshControl"]; | |
[self addSubview: refreshControl]; | |
objc_setAssociatedObject(self, @"iOS5RefreshControl", refreshControl, OBJC_ASSOCIATION_RETAIN); | |
} | |
return self; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment