Skip to content

Instantly share code, notes, and snippets.

@paul-delange
Last active December 15, 2015 04:59
Show Gist options
  • Save paul-delange/5205515 to your computer and use it in GitHub Desktop.
Save paul-delange/5205515 to your computer and use it in GitHub Desktop.
Storyboard support for ISRefreshControl
#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
#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
#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