Friday, May 29, 2009

Dynamically Creating Views/Controllers

I had to solve a problem recently where I needed to dynamically create a view controller from a table controller, not knowing at compile time what the new class would be (ie. it could be one of many different class types). In order to solve this problem fully, I created a new class, which I call a ClassConstructor, and some code to execute the class constructor at the appropriate time.

The ClassConstructor does the heavy lifting of allocating and initializing the new class. In order to do so, it needs to know what Class it is creating, and how to initialize it. So the interface for this class is :

@interface ClassConstructor : NSObject {

@private 

NSInvocation *_invocation;

NSMethodSignature *_method;

Class _class;

}

@property (nonatomic, retain) NSInvocation *_invocation;

@property (nonatomic, retain) NSMethodSignature *_method;


- (id)initWithClass:(Class)iClass method:(NSString *)iMethod, ...;

- (id)create;

@end

The implementation for the ClassConstructor class is:

@implementation ClassConstructor

@synthesize _invocation, _method;


- (id)initWithClass:(Class)iClass method:(NSString *)iMethod, ... {

if (self = [super init]) {

_class = iClass;

if (iMethod) {

SEL aSelector = NSSelectorFromString(iMethod);

self._method = [iClass instanceMethodSignatureForSelector:aSelector];

self._invocation = [NSInvocation invocationWithMethodSignature:_method];

[_invocation retainArguments];

[_invocation setSelector:aSelector];

NSUInteger aCount = [_method numberOfArguments];

va_list anArgList;

va_start(anArgList, iMethod);

for (NSUInteger anIndex = 2; anIndex

void *aPointerArg = va_arg(anArgList, void *);

[_invocation setArgument:&aPointerArg atIndex:anIndex];

}

va_end(anArgList);

}

}

return self;

}



- (void)dealloc {

[_invocation release];

[_method release];

[super dealloc];

}



- (id)create {

id aReturnVal = nil;

// alloc and set the target

[_invocation setTarget:[_class alloc]];

// invoke the constructor

[_invocation invoke];

// get the result

if (_method.methodReturnLength) {

[_invocation getReturnValue:&aReturnVal];

}


return aReturnVal;

}

@end



To create an instance of a ClassConstructor, you would generally do it like so :

ClassConstructor *aClassConstructor = [[[ClassConstructor alloc] initWithClass:[FooController class] method:@"initWithStyle:", UITableViewStylePlain] autorelease];


which creates a new ClassConstructor that will allocate and initialize an instance of the table controller FooController.

Using this, in conjunction with CellControllers (as outlined at http://cocoawithlove.com/2008/12/heterogeneous-cells-in.html), you can create truly dynamic MVC tables using a shared cell controller.

No comments:

Post a Comment