This repository has been archived by the owner. It is now read-only.

Improve interoperability between CKRefreshControl and UIRefreshControl classes #9

Merged
merged 18 commits into from Jan 28, 2013
Commits
Jump to file or symbol
Failed to load files and symbols.
+129 −92
Diff settings

Always

Just for now

@@ -23,8 +23,6 @@
#import "CKParagraphStyle.h"
#import <objc/objc-runtime.h>
static BOOL _isMasquerading = NO;
@implementation CKParagraphStyle
#if __IPHONE_OS_VERSION_MAX_ALLOWED <= __IPHONE_5_1
@@ -33,8 +31,6 @@ @implementation CKParagraphStyle
#define IMP_WITH_BLOCK_TYPE id
#endif
static void *NSParagraphStyleKey;
- (id) initWithCoder: (NSCoder *) aDecoder
{
return [super init];
@@ -48,26 +44,12 @@ + (void) load
// CKParagraphStyle will masquerade as NSParagraphStyle
static dispatch_once_t registerNSParagraphStyleClass_onceToken;
dispatch_once(&registerNSParagraphStyleClass_onceToken, ^{
Class nsParagraphStyleClass = objc_allocateClassPair([self class], "NSParagraphStyle", 0);
objc_registerClassPair(nsParagraphStyleClass);
_isMasquerading = YES;
Class nsAttributedStringClass = [NSAttributedString class];
IMP paragraphStyleIMP = imp_implementationWithBlock((IMP_WITH_BLOCK_TYPE)(^NSParagraphStyle *(id dynamicSelf) {
return objc_getAssociatedObject(dynamicSelf, &NSParagraphStyleKey);
}));
BOOL added = class_addMethod(nsAttributedStringClass, @selector(paragraphStyle), paragraphStyleIMP, "@@:");
NSAssert(added, @"We tried to add the paragraphStyle method, and it failed. This is going to break things, so we may as well stop here.");
IMP setParagraphStyleIMP = imp_implementationWithBlock((IMP_WITH_BLOCK_TYPE)(^void(NSAttributedString *dynamicSelf, id paragraphStyle) {
if (dynamicSelf.paragraphStyle == paragraphStyle) {
return;
}
objc_setAssociatedObject(dynamicSelf, &NSParagraphStyleKey, paragraphStyle, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}));
added = class_addMethod(nsAttributedStringClass, @selector(setParagraphStyle:), setParagraphStyleIMP, "v@:@");
NSAssert(added, @"We tried to add the setParagraphStyle: method, and it failed. This is going to break things, so we may as well stop here.");
Class nsParagraphStyleClass = NSClassFromString(@"NSParagraphStyle");
if (!nsParagraphStyleClass)
{
nsParagraphStyleClass = objc_allocateClassPair(self, "NSParagraphStyle", 0);
objc_registerClassPair(nsParagraphStyleClass);
}
});
}
@@ -36,8 +36,6 @@
CKRefreshControlStateRefreshing
} CKRefreshControlState;
static BOOL _isMasquerading = NO;
@interface CKRefreshControl ()
@property (nonatomic) CKRefreshControlState refreshControlState;
@end
@@ -52,10 +50,8 @@ @implementation CKRefreshControl {
- (id)init
{
Class uiRefreshControlClass = NSClassFromString(@"UIRefreshControl");
if (uiRefreshControlClass && !_isMasquerading)
return [[uiRefreshControlClass alloc] init];
if (![[UIRefreshControl class] isSubclassOfClass:[CKRefreshControl class]])
return (id)[[UIRefreshControl alloc] init];
if (self = [super init])
{
@@ -79,14 +75,10 @@ - (id) initWithCoder:(NSCoder *)aDecoder
self.attributedTitle = [aDecoder decodeObjectForKey:@"UIAttributedTitle"];
// we can set its refresh control when the table view controller sets its view
[[NSNotificationCenter defaultCenter] addObserverForName: CKRefreshControl_UITableViewController_DidSetView_Notification
object: nil
queue: nil
usingBlock: ^(NSNotification *notification) {
UITableViewController *tableViewController = notification.object;
if (tableViewController.refreshControl != (id)self)
tableViewController.refreshControl = (id)self;
} ];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: @selector(tableViewControllerDidSetView:)
name: CKRefreshControl_UITableViewController_DidSetView_Notification
object: nil ];
}
return self;
}
@@ -98,10 +90,23 @@ - (void) commonInit
[self setRefreshControlState:CKRefreshControlStateHidden];
}
- (void) tableViewControllerDidSetView: (NSNotification *) notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:CKRefreshControl_UITableViewController_DidSetView_Notification
object:nil];
UITableViewController *tableViewController = notification.object;
if (tableViewController.refreshControl != (id)self)
tableViewController.refreshControl = (id)self;
}
// remove notification observer in case notification never fired
- (void) awakeFromNib
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:CKRefreshControl_UITableViewController_DidSetView_Notification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver: self
name: CKRefreshControl_UITableViewController_DidSetView_Notification
object: nil ];
[super awakeFromNib];
}
@@ -131,10 +136,11 @@ - (void)populateSubviews {
[self addSubview:spinner];
}
- (void)setTintColor:(UIColor *)tintColor {
if (tintColor == nil) {
- (void)setTintColor: (UIColor *) tintColor
{
if (!tintColor)
tintColor = [UIColor colorWithWhite:0.5 alpha:1];
}
textLabel.textColor = tintColor;
arrow.tintColor = tintColor;
spinner.color = tintColor;
@@ -355,16 +361,29 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
#pragma mark - Class methods
+ (Class) class
{
// cannot call +class from inside this method
Class uiRefreshControlClass = NSClassFromString(@"UIRefreshControl");
Class ckRefreshControlClass = NSClassFromString(@"CKRefreshControl");
if (![self isSubclassOfClass:ckRefreshControlClass])
return uiRefreshControlClass;
return [super class];
}
// If UIRefreshControl is available, we need to customize that class, not
// CKRefreshControl. Otherwise, the +appearance proxy is broken on iOS 6.
+ (id)appearance {
Class uiRefreshControlClass = NSClassFromString(@"UIRefreshControl");
if (uiRefreshControlClass) {
+ (id)appearance
{
if (![[UIRefreshControl class] isSubclassOfClass:[CKRefreshControl class]])
return [UIRefreshControl appearance];
}
else {
return [super appearance];
}
if ([self isEqual:[UIRefreshControl class]])
return [CKRefreshControl appearance];
return [super appearance];
}
+ (id)appearanceWhenContainedIn:(Class<UIAppearanceContainer>)ContainerClass, ... {
@@ -382,28 +401,16 @@ + (id)appearanceWhenContainedIn:(Class<UIAppearanceContainer>)ContainerClass, ..
classes[i] = c;
}
va_end(list);
Class uiRefreshControlClass = NSClassFromString(@"UIRefreshControl");
if (uiRefreshControlClass) {
return [UIRefreshControl appearanceWhenContainedIn:ContainerClass, classes[0], classes[1], classes[2], classes[3], classes[4], classes[5], classes[6], classes[7], classes[8], classes[9], nil];
} else {
return [super appearanceWhenContainedIn:ContainerClass, classes[0], classes[1], classes[2], classes[3], classes[4], classes[5], classes[6], classes[7], classes[8], classes[9], nil];
}
}
// This is overridden so that things like
// [control isKindOfClass:[CKRefreshControl class]]
// will work on both iOS 5 and iOS 6.
+ (Class)class {
Class uiRefreshControlClass = NSClassFromString(@"UIRefreshControl");
if (![[UIRefreshControl class] isSubclassOfClass:[CKRefreshControl class]])
return [UIRefreshControl appearanceWhenContainedIn:ContainerClass, classes[0], classes[1], classes[2], classes[3], classes[4], classes[5], classes[6], classes[7], classes[8], classes[9], nil];
if (uiRefreshControlClass)
return uiRefreshControlClass;
if ([self isEqual:[UIRefreshControl class]])
return [CKRefreshControl appearanceWhenContainedIn:ContainerClass, classes[0], classes[1], classes[2], classes[3], classes[4], classes[5], classes[6], classes[7], classes[8], classes[9], nil];
return [super class];
return [super appearanceWhenContainedIn:ContainerClass, classes[0], classes[1], classes[2], classes[3], classes[4], classes[5], classes[6], classes[7], classes[8], classes[9], nil];
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED <= __IPHONE_5_1
#define IMP_WITH_BLOCK_TYPE __bridge void*
#else
@@ -451,10 +458,32 @@ + (void)load
// CKRefreshControl will masquerade as UIRefreshControl
static dispatch_once_t registerUIRefreshControlClass_onceToken;
dispatch_once(&registerUIRefreshControlClass_onceToken, ^{
Class uiRefreshControlClass = objc_allocateClassPair([self class], "UIRefreshControl", 0);
objc_registerClassPair(uiRefreshControlClass);
_isMasquerading = YES;
Class *UIRefreshControlClassRef = NULL;
#if TARGET_CPU_ARM
__asm(
"movw %0, :lower16:(L_OBJC_CLASS_UIRefreshControl-(LPC0+4))\n"
"movt %0, :upper16:(L_OBJC_CLASS_UIRefreshControl-(LPC0+4))\n"
"LPC0: add %0, pc" : "=r"(UIRefreshControlClassRef)
);
#elif TARGET_CPU_X86_64
__asm("leaq L_OBJC_CLASS_UIRefreshControl(%%rip), %0" : "=r"(UIRefreshControlClassRef));
#elif TARGET_CPU_X86
void *pc = NULL;
__asm(
"calll L0\n"
"L0: popl %0\n"
"leal L_OBJC_CLASS_UIRefreshControl-L0(%0), %1" : "=r"(pc), "=r"(UIRefreshControlClassRef)
);
#else
#error Unsupported CPU
#endif
if (UIRefreshControlClassRef && *UIRefreshControlClassRef == Nil)
{
*UIRefreshControlClassRef = objc_allocateClassPair(self, "UIRefreshControl", 0);
objc_registerClassPair(*UIRefreshControlClassRef);
}

This comment has been minimized.

@0xced

0xced Jan 28, 2013

For full compatibility, I think you should use objc_duplicateClass(self, "UIRefreshControl", 0); instead of objc_allocateClassPair + objc_registerClassPair since this will add an intermediate CKRefreshControl class in the hierarchy.

@0xced

0xced Jan 28, 2013

For full compatibility, I think you should use objc_duplicateClass(self, "UIRefreshControl", 0); instead of objc_allocateClassPair + objc_registerClassPair since this will add an intermediate CKRefreshControl class in the hierarchy.

This comment has been minimized.

@johnhaitas

johnhaitas Jan 28, 2013

Contributor

I deliberately took this approach rather than objc_duplicateClass so we can later test if UIRefreshControl is a subclass of CKRefreshControl. This was particularly useful with the +appearance proxy.

I used objc_duplicateClass for CKParagraphStyle.

@johnhaitas

johnhaitas Jan 28, 2013

Contributor

I deliberately took this approach rather than objc_duplicateClass so we can later test if UIRefreshControl is a subclass of CKRefreshControl. This was particularly useful with the +appearance proxy.

I used objc_duplicateClass for CKParagraphStyle.

This comment has been minimized.

@johnhaitas

johnhaitas Jan 28, 2013

Contributor

I borrowed the subclassing idea from @steipete's PSTCollectionView

@bjhomer would it be ok to acknowledge @0xced and @steipete in the README.md file?

@johnhaitas

johnhaitas Jan 28, 2013

Contributor

I borrowed the subclassing idea from @steipete's PSTCollectionView

@bjhomer would it be ok to acknowledge @0xced and @steipete in the README.md file?

This comment has been minimized.

@bjhomer

bjhomer Jan 28, 2013

Contributor

I have no problem with that

@bjhomer

bjhomer Jan 28, 2013

Contributor

I have no problem with that

// Add UITableViewController.refreshControl if it isn't present
Class tableViewController = [UITableViewController class];
IMP refreshControlIMP = imp_implementationWithBlock((IMP_WITH_BLOCK_TYPE)(^id(id dynamicSelf) {
@@ -488,4 +517,28 @@ + (void)load
});
}
__asm(
#if defined(__OBJC2__) && __OBJC2__
".section __DATA,__objc_classrefs,regular,no_dead_strip\n"
#if TARGET_RT_64_BIT
".align 3\n"
"L_OBJC_CLASS_UIRefreshControl:\n"
".quad _OBJC_CLASS_$_UIRefreshControl\n"
#else
".align 2\n"
"L_OBJC_CLASS_UIRefreshControl:\n"
".long _OBJC_CLASS_$_UIRefreshControl\n"
#endif
#else
".section __TEXT,__cstring,cstring_literals\n"
"L_OBJC_CLASS_NAME_UIRefreshControl:\n"
".asciz \"UIRefreshControl\"\n"
".section __OBJC,__cls_refs,literal_pointers,no_dead_strip\n"
".align 2\n"
"L_OBJC_CLASS_UIRefreshControl:\n"
".long L_OBJC_CLASS_NAME_UIRefreshControl\n"
#endif
".weak_reference _OBJC_CLASS_$_UIRefreshControl\n"
);
@end
View
@@ -1,16 +1,15 @@
# CKRefreshControl
Open source 100% API-compatible replacement for `UIRefreshControl`, supporting iOS 5.0+
Open source 100% API-compatible replacement for [UIRefreshControl](http://developer.apple.com/library/ios/#documentation/uikit/reference/UIRefreshControl_class/Reference/Reference.html) , supporting iOS 5.0+
Using it is as simple as this:
#import <CKRefreshControl/CKRefreshControl.h>
UITableViewController *controller;
CKRefreshControl *refreshControl = [CKRefreshControl new];
[refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
controller.refreshControl = (id)refreshControl;
controller.refreshControl = [[UIRefreshControl alloc] init];
[controller.refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
Then just link against the static library the `CKRefreshControl` project provides, and you're ready to go.
@@ -21,14 +20,12 @@ Then just link against the static library the `CKRefreshControl` project provide
### API Compatible
CKRefreshControl has exactly the same public API as UIRefreshControl. Thus, your code can treat either one as an instance of the other. Just use CKRefreshControl instead of UIRefreshControl everywhere, and you'll magically get iOS 5 support.
CKRefreshControl has exactly the same public API as UIRefreshControl. Thus, your code can treat either one as an instance of the other. Just use `CKRefreshControl` or `UIRefreshControl` throughout your code, and you'll magically get iOS 5 support.
We take advantage of this to provide excellent iOS 6 compatibility. When running on an iOS 6 device, `-[CKRefreshControl init]` actually returns a `UIRefreshControl` instance. No CKRefreshControl is ever initialized, and everything will work exactly as if CKRefreshControl did not even exist.
You can even use the `+appearance` proxies introduced in iOS 5.0; CKRefreshControl will appropriately forward the customizations to UIRefreshControl on iOS 6.
There is only one minor inconvenience. The `-[UITableViewController setRefreshControl:]` method in iOS 6 accepts a `UIRefreshControl *` parameter. Although CKRefreshControl is API compatible with UIRefreshControl, it is not a subclass (so it can be used on iOS 5). Thus, the compiler does not know that CKRefreshControl can be used as a UIRefreshControl. Thus, you must add an explicit cast to avoid a compiler warning. This is completely safe.
---
### Appearance
@@ -51,6 +48,15 @@ Yes! Instructure is using it in shipping code with no problems. That said, if yo
---
### Contributors
* [@bjhomer](http://github.com/bjhomer)
* [@johnhaitas](http://github.com/johnhaitas)
* [@steipete](http://github.com/steipete)
* [@0xced](http://github.com/0xced)
---
### License
CKRefreshControl, and all the accompanying source code, is released under the MIT license. You can see the full text of the license in the accompanying LICENSE.txt file.
@@ -30,7 +30,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(
{
application.statusBarStyle = UIStatusBarStyleBlackOpaque;
[[CKRefreshControl appearanceWhenContainedIn:[AppearanceCustomizationController class], [UITabBarController class], nil] setTintColor:[UIColor greenColor]];
[[UIRefreshControl appearanceWhenContainedIn:[AppearanceCustomizationController class], nil] setTintColor:[UIColor greenColor]];
// Override point for customization after application launch.
return YES;
@@ -29,9 +29,8 @@ - (void)viewDidLoad
{
[super viewDidLoad];
CKRefreshControl *refreshControl = [CKRefreshControl new];
[refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
self.refreshControl = (id)refreshControl;
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
}
- (void)doRefresh:(CKRefreshControl *)sender {
@@ -39,10 +39,9 @@ - (void)viewDidLoad
@"Black" : [UIColor blackColor],
};
CKRefreshControl *refreshControl = [CKRefreshControl new];
refreshControl.tintColor = [UIColor orangeColor];
[refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
self.refreshControl = (id)refreshControl;
self.refreshControl = [[UIRefreshControl alloc] init];
self.refreshControl.tintColor = [UIColor orangeColor];
[self.refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
}
- (void)doRefresh:(CKRefreshControl *)sender {
@@ -29,9 +29,8 @@ - (void)viewDidLoad
{
[super viewDidLoad];
CKRefreshControl *refreshControl = [CKRefreshControl new];
[refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
self.refreshControl = (id)refreshControl;
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
}
- (void)doRefresh:(CKRefreshControl *)sender {
@@ -32,11 +32,10 @@ @implementation TextRefreshingController
- (void)viewDidLoad
{
[super viewDidLoad];
CKRefreshControl *refreshControl = [CKRefreshControl new];
refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"This is a test"];
[refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
self.refreshControl = (id)refreshControl;
self.refreshControl = [[UIRefreshControl alloc] init];
self.refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@"This is a test"];
[self.refreshControl addTarget:self action:@selector(doRefresh:) forControlEvents:UIControlEventValueChanged];
}
- (void)doRefresh:(CKRefreshControl *)sender {
ProTip! Use n and p to navigate between commits in a pull request.