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

Storyboard support #8

Merged
merged 22 commits into from Jan 24, 2013
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
98871ab
Initial implementation of storyboard support
johnhaitas Jan 18, 2013
4dc2e23
adding StoryboardViewController to RefreshControlDemo app to demonstr…
johnhaitas Jan 18, 2013
88ab570
improvement of initWithCoder method and consolidation of common initi…
johnhaitas Jan 19, 2013
53e4f4e
adding tint color to storyboard
johnhaitas Jan 19, 2013
ff89652
use dispatch_once to be sure we register the class only once
johnhaitas Jan 19, 2013
37b294b
use 'initialize' rather than 'class' class method to fire the 'load' …
johnhaitas Jan 19, 2013
45bb190
CKRefreshControl's attributedTitle is being read from storyboard in i…
johnhaitas Jan 19, 2013
3dc3d03
fix for iOS 6
johnhaitas Jan 19, 2013
a6082c3
comment fix
johnhaitas Jan 19, 2013
dea34f3
rename category for NSAttributedString
johnhaitas Jan 21, 2013
f42dafa
renaming JHParagraphStyle to CKParagraphStyle
johnhaitas Jan 23, 2013
d100e6c
Better test for whether or not classes are already defined
johnhaitas Jan 23, 2013
b0a0048
adding correct license to storyboard class
johnhaitas Jan 23, 2013
13b2eb1
only set tableViewController.refreshControl if it is not already == self
johnhaitas Jan 23, 2013
6122906
don't call [super initialize] from CKRefreshControl +initialize
johnhaitas Jan 24, 2013
2fe4045
relocating class_swizzleMethodAndStore and making it static
johnhaitas Jan 24, 2013
f72826d
making CKParagraphStyle.h is now a private header
johnhaitas Jan 24, 2013
1d1acda
oops ... copy paste error
johnhaitas Jan 24, 2013
83ab68e
cleanup of +class method
johnhaitas Jan 24, 2013
1e9a77e
removing useless CKParagraphStyle +class method
johnhaitas Jan 24, 2013
b5c30ca
distinguishing appearance from Default view controller
johnhaitas Jan 24, 2013
8b60a53
add -ObjC linker flag to RefreshControlDemo means we no longer have t…
johnhaitas Jan 24, 2013
File filter...
Filter file types
Jump to…
Jump to file
Failed to load files.

Always

Just for now

@@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
589E4E5C16AB267E00E9E0DF /* CKParagraphStyle.m in Sources */ = {isa = PBXBuildFile; fileRef = 589E4E5B16AB267E00E9E0DF /* CKParagraphStyle.m */; };
B71610F61631C4EB00B08595 /* CKRefreshControl.m in Sources */ = {isa = PBXBuildFile; fileRef = B71610F51631C4EB00B08595 /* CKRefreshControl.m */; };
B71610F71631C4F400B08595 /* CKRefreshControl.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B71610F41631C4EB00B08595 /* CKRefreshControl.h */; };
B71610FA1631C54E00B08595 /* CKRefreshArrowView.m in Sources */ = {isa = PBXBuildFile; fileRef = B71610F91631C54E00B08595 /* CKRefreshArrowView.m */; };
@@ -26,6 +27,8 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
589E4E5A16AB267E00E9E0DF /* CKParagraphStyle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKParagraphStyle.h; sourceTree = "<group>"; };
589E4E5B16AB267E00E9E0DF /* CKParagraphStyle.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CKParagraphStyle.m; sourceTree = "<group>"; };
B71610E31631C4BC00B08595 /* libCKRefreshControl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCKRefreshControl.a; sourceTree = BUILT_PRODUCTS_DIR; };
B71610EA1631C4BC00B08595 /* CKRefreshControl-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CKRefreshControl-Prefix.pch"; sourceTree = "<group>"; };
B71610F41631C4EB00B08595 /* CKRefreshControl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CKRefreshControl.h; sourceTree = "<group>"; };
@@ -69,6 +72,8 @@
B71610F51631C4EB00B08595 /* CKRefreshControl.m */,
B71610F81631C54E00B08595 /* CKRefreshArrowView.h */,
B71610F91631C54E00B08595 /* CKRefreshArrowView.m */,
589E4E5A16AB267E00E9E0DF /* CKParagraphStyle.h */,
589E4E5B16AB267E00E9E0DF /* CKParagraphStyle.m */,
);
path = CKRefreshControl;
sourceTree = "<group>";
@@ -134,6 +139,7 @@
files = (
B71610F61631C4EB00B08595 /* CKRefreshControl.m in Sources */,
B71610FA1631C54E00B08595 /* CKRefreshArrowView.m in Sources */,
589E4E5C16AB267E00E9E0DF /* CKParagraphStyle.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -0,0 +1,35 @@
// CKParagraphStyle.h
//
// Copyright (c) 2012 Instructure, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#import <Foundation/Foundation.h>

@interface CKParagraphStyle : NSObject

@end


@class NSAttributedString;
@class NSParagraphStyle;
@interface NSAttributedString (CKParagraphStyleAdditions)
// This will be added to the class at runtime if not already available
@property (nonatomic,retain) NSParagraphStyle *paragraphStyle;
@end
@@ -0,0 +1,74 @@
// CKParagraphStyle.m
//
// Copyright (c) 2012 Instructure, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#import "CKParagraphStyle.h"
#import <objc/objc-runtime.h>

static BOOL _isMasquerading = NO;

@implementation CKParagraphStyle

#if __IPHONE_OS_VERSION_MAX_ALLOWED <= __IPHONE_5_1
#define IMP_WITH_BLOCK_TYPE __bridge void*
#else
#define IMP_WITH_BLOCK_TYPE id
#endif

static void *NSParagraphStyleKey;

- (id) initWithCoder: (NSCoder *) aDecoder
{
return [super init];
}

+ (void) load
{
if (objc_getClass("NSParagraphStyle"))
return;

// 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.");
});
}

@end
@@ -22,6 +22,7 @@

#import "CKRefreshControl.h"
#import "CKRefreshArrowView.h"
#import "CKParagraphStyle.h"
#import <objc/runtime.h>

#if !__has_feature(objc_arc)
@@ -35,6 +36,7 @@
CKRefreshControlStateRefreshing
} CKRefreshControlState;

static BOOL _isMasquerading = NO;

@interface CKRefreshControl ()
@property (nonatomic) CKRefreshControlState refreshControlState;
@@ -51,22 +53,58 @@ @implementation CKRefreshControl {
- (id)init
{
Class uiRefreshControlClass = NSClassFromString(@"UIRefreshControl");
if (uiRefreshControlClass) {
return [[uiRefreshControlClass alloc] init];
}

CGRect defaultFrame = CGRectMake(0, 0, 320, 60);
self = [super initWithFrame:defaultFrame];
if (self) {
// Initialization code
if (uiRefreshControlClass && !_isMasquerading)
return [[uiRefreshControlClass alloc] init];

if (self = [super init])
{
[self commonInit];
self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin;
self.backgroundColor = [UIColor clearColor];
[self populateSubviews];
[self setRefreshControlState:CKRefreshControlStateHidden];
}
return self;
}

- (id) initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
[self commonInit];

if ([aDecoder containsValueForKey:@"UITintColor"])
self.tintColor = (UIColor *)[aDecoder decodeObjectForKey:@"UITintColor"];

if ([aDecoder containsValueForKey:@"UIAttributedTitle"])
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;
} ];
}
return self;
}

- (void) commonInit
{
self.frame = CGRectMake(0, 0, 320, 60);
[self populateSubviews];
[self setRefreshControlState:CKRefreshControlStateHidden];
}

// remove notification observer in case notification never fired
- (void) awakeFromNib
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:CKRefreshControl_UITableViewController_DidSetView_Notification object:nil];
[super awakeFromNib];
}

- (void)populateSubviews {
CGRect frame = CGRectInset(self.bounds, 12, 12);
arrow = [[CKRefreshArrowView alloc] initWithFrame:frame];
@@ -220,7 +258,8 @@ - (void)didMoveToSuperview {
if ([superview isKindOfClass:[UITableView class]]) {
UITableViewController *tableViewController = (UITableViewController *)superview.nextResponder;
if ([tableViewController isKindOfClass:[UITableViewController class]]) {
tableViewController.refreshControl = (id)self;
if (tableViewController.refreshControl != (id)self)
tableViewController.refreshControl = (id)self;
}
}
}
@@ -357,12 +396,11 @@ + (id)appearanceWhenContainedIn:(Class<UIAppearanceContainer>)ContainerClass, ..
// will work on both iOS 5 and iOS 6.
+ (Class)class {
Class uiRefreshControlClass = NSClassFromString(@"UIRefreshControl");
if (uiRefreshControlClass) {

if (uiRefreshControlClass)
return uiRefreshControlClass;
}
else {
return [super class];
}

return [super class];
}


@@ -372,38 +410,82 @@ + (Class)class {
#define IMP_WITH_BLOCK_TYPE id
#endif

typedef IMP *IMPPointer;

static void *CKRefreshControlKey;
+ (void)load {
if ([UITableViewController instancesRespondToSelector:@selector(refreshControl)]) {
return;
}

// Add UITableViewController.refreshControl if it isn't present

Class tableViewController = [UITableViewController class];
IMP refreshControlIMP = imp_implementationWithBlock((IMP_WITH_BLOCK_TYPE)(^id(id dynamicSelf) {
return objc_getAssociatedObject(dynamicSelf, &CKRefreshControlKey);
}));
BOOL added = class_addMethod(tableViewController, @selector(refreshControl), refreshControlIMP, "@@:");

NSAssert(added, @"We tried to add the refreshControl method, and it failed. This is going to break things, so we may as well stop here.");


static BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store)
{
IMP imp = NULL;
Method method = class_getInstanceMethod(class, original);

IMP setRefreshControlIMP = imp_implementationWithBlock((IMP_WITH_BLOCK_TYPE)(^void(UITableViewController *dynamicSelf, id newControl) {
if (dynamicSelf.refreshControl == newControl) {
return;
}
objc_setAssociatedObject(dynamicSelf, &CKRefreshControlKey, newControl, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
if (method)
{
const char *type = method_getTypeEncoding(method);
imp = class_replaceMethod(class, original, replacement, type);

if ([newControl superview] != dynamicSelf.tableView) {
[dynamicSelf.tableView addSubview:newControl];
}
}));
if (!imp)
imp = method_getImplementation(method);
}

if (imp && store)
*store = imp;

added = class_addMethod(tableViewController, @selector(setRefreshControl:), setRefreshControlIMP, "v@:@");
NSAssert(added, @"We tried to add the setRefreshControl: method, and it failed. This is going to break things, so we may as well stop here.");
return (imp != NULL);
}

NSString *const CKRefreshControl_UITableViewController_DidSetView_Notification = @"CKRefreshControl_UITableViewController_DidSetView";
static void CKRefreshControl_UITableViewController_SetView(UITableViewController *dynamicSelf, SEL _cmd, UITableView *view);
static void (*UITableViewController_SetViewIMP)(UITableViewController *dynamicSelf, SEL _cmd, UITableView *view);
static void CKRefreshControl_UITableViewController_SetView(UITableViewController *dynamicSelf, SEL _cmd, UITableView *view)
{
UITableViewController_SetViewIMP(dynamicSelf, _cmd, view);
[[NSNotificationCenter defaultCenter] postNotificationName:CKRefreshControl_UITableViewController_DidSetView_Notification object:dynamicSelf];
}

+ (void)load
{
if (objc_getClass("UIRefreshControl"))
return;

@end
// 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;

// Add UITableViewController.refreshControl if it isn't present
Class tableViewController = [UITableViewController class];
IMP refreshControlIMP = imp_implementationWithBlock((IMP_WITH_BLOCK_TYPE)(^id(id dynamicSelf) {
return objc_getAssociatedObject(dynamicSelf, &CKRefreshControlKey);
}));
BOOL added = class_addMethod(tableViewController, @selector(refreshControl), refreshControlIMP, "@@:");

NSAssert(added, @"We tried to add the refreshControl method, and it failed. This is going to break things, so we may as well stop here.");


IMP setRefreshControlIMP = imp_implementationWithBlock((IMP_WITH_BLOCK_TYPE)(^void(UITableViewController *dynamicSelf, id newControl) {
if (dynamicSelf.refreshControl == newControl) {
return;
}
objc_setAssociatedObject(dynamicSelf, &CKRefreshControlKey, newControl, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

if ([newControl superview] != dynamicSelf.tableView) {
[dynamicSelf.tableView addSubview:newControl];
}
}));

added = class_addMethod(tableViewController, @selector(setRefreshControl:), setRefreshControlIMP, "v@:@");
NSAssert(added, @"We tried to add the setRefreshControl: method, and it failed. This is going to break things, so we may as well stop here.");

// swizzle setView: method so we can post notification to CKRefreshControl ...
// CKRefreshControl will assign itself to the UIViewController
class_swizzleMethodAndStore(tableViewController,
@selector(setView:),
(IMP)CKRefreshControl_UITableViewController_SetView,
(IMP *)&UITableViewController_SetViewIMP );
});
}

@end
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.