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.

Monday, May 11, 2009

UISearchBar like Contacts

This is the inaugural posting for this blog, and I picked a topic that I have seen many times on the discussion forums in the past, and one I solved for myself last fall.

When using a UISearchBar with an indexed table view, the index will get written on top of the search field in the search bar, resulting in an ugly UI that hopefully no one would ever inflict on their users.

To solve the problem, I have subclassed UISearchBar and overridden the layoutSubviews method to draw differently when there is an index. When I am using an indexed table with search, I just set the hasIndex attribute to YES. Here is the code :

- (void)layoutSubviews {
[super layoutSubviews];
BOOL hasButton = NO;
if (hasIndex) {
UITextField *aSearchField = nil;
for (UIView *aView in [self subviews]) {
if ([aView isKindOfClass:[UITextField class]]) {
aSearchField = (UITextField *)aView;
} else if ([aView isKindOfClass:[UIButton class]]) {
hasButton = YES;
}
}
if (aSearchField && !hasButton) {
aSearchField.frame = CGRectMake(aSearchField.frame.origin.x, aSearchField.frame.origin.y, aSearchField.frame.size.width -30, aSearchField.frame.size.height);
}
}
}

The code checks for our hasIndex attribute, and if it is true, changes the layout of the search bar by removing 30 pixels from the right-hand side of the search text field. If there is a Cancel button on the search field, we do nothing.