Standalone Delegate Objects
It is very common practice that an object like a view controller is the delgate (and perhaps datasource) for another encapsulated or included object, such as a table view. This affords easy access from the delegate methods to the delegate object. But when might you want to use a delegate object that is not your main view controller? And why?
The answers are: rarely and it depends.
Recently I faced this exact dilemma, and the solution, although complex, was to use two different delegate objects with a total of 4 data sources for a single table view within a view controller! Madness! But then, the requirements were madness.
We needed to display a table with a search bar and two visible scope selectors. The scope selectors indicated the type of data one could search for with the search bar.
In scope “A”, typing in the search bar queried a server for results in dataset “A” matching the search entry. This data came back as JSON, neatly stored in an NSArray. That was the data source for this mode, managed by the first delegate object.
In scope “B”, however, there were three different data sources: a table of organizations (1) into which the user could drill down for a list of units within an organization (2), and a search bar which allowed the user to search among the units of all organizations (3). Scope “B” therefore had three modes, which was managed by the second delegate, which also controlled the table (and drill down table, which was a very lightweight custom UITableViewController declared in the scope “B” delegate.) Each mode used a separate NSFetchedResultsController to populate the table based on a predicate appropriate for the current mode.
Now don’t forget that in all this, the actual tableView object is owned by the top-level UIViewController. It, the delegate objects, as well as the drill-down UITableViewController all operate with this one table. Ultimately, the delegate objects are doing exactly what they are intended to do, which is fill the table with the right data at the right time.
If your head is spinning, just wait. Let’s see how this madness comes together.
At the top level, I had a UIViewController that I could create with zero or more scopes. In this case, I created it with two scopes, each of which had a name and the Class of the appropriate delegate object responsible for that scope. The constructor for the UIViewController created an array of these delegate objects based on the Class names passed in, as well as setup the search bar scope labels.
[code lang=”objc”]
NSDictionary *scopesAndDelegates = @{@"Scope A" : [DelegateScopeA class],
@"Scope B" : [DelegateScopeB class] };
ScopeViewConrtoller *vc = [[[ScopeViewController alloc] initWithFrame:self.view.frame
withType:_groupType
withScopesAndDelegates:scopesAndDelegates] autorelease];
…
[/code]
[code lang=”objc”]
-(id)initWithFrame:(CGRect)frame withType:(NSString *)groupType withScopesAndDelegates:(NSDictionary *)scopesAndDelegates
{
self = [self initWithFrame:frame withType:groupType];
if (self) {
if (scopesAndDelegates && scopesAndDelegates.count > 0) {
_delegates = [[[NSMutableDictionary alloc] initWithDictionary:scopesAndDelegates] retain];
__block NSMutableArray *scopes = [NSMutableArray array];
[scopesAndDelegates enumerateKeysAndObjectsUsingBlock:^(NSString *key, Class delegateClass, BOOL *stop) {
[scopes addObject:[key capitalizedString]];
DelegateBase *d = [[[delegateClass alloc] initWithMainViewController:self processor:processor] autorelease];
[_delegates setObject:d forKey:key];
}];
[mySearchBar setScopeButtonTitles:scopes];
mySearchBar.showsScopeBar = YES;
[mySearchBar setSelectedScopeButtonIndex:0];
[_defaultDelegate release];
_defaultDelegate = nil;
}
}
return self;
}
[/code]
Suffice to say that the main UIViewController was the primary delegate for the included UITableView, UISearchBar, and UISearchDisplayController, and most if not all of the delegate protocol methods for these objects had to be implemented, but they were, for the most part, very short. The reason for this is that, depending on which scope was in play, the top-level delegate methods would try to invoke the same method on the appropriate “sub”-delegate, something like this:
[code lang=”objc”]
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
if ([[self helper] respondsToSelector:@selector(tableView:heightForHeaderInSection:)])
return [[self helper] tableView:tableView heightForHeaderInSection:section];
return 0;
}
[/code]
This kind of pattern is repeated for most delegate methods, at the top level. A good part of the magic is in the -helper: method:
[code lang=”objc”]
-(DelegateBase *)helper
{
return [self helperForScopeIndex:[mySearchBar selectedScopeButtonIndex]];
}
-(DelegateBase *)helperForScopeIndex:(NSInteger)scopeIndex
{
if (!_delegates)
return _defaultDelegate;
NSString *key = [[mySearchBar scopeButtonTitles] objectAtIndex:scopeIndex];
DelegateBase *d = [_delegates objectForKey:key];
return d;
}
[/code]
Dynamically, the appropriate delegate object is chosen based on which scope is selected. (The top-level UIViewController class also allows for no scopes, and the so-called “default delegate” object is created in that case and returned in lieu of there being an array of per-scope delegates.)
The DelegateBase class conforms to all the same table view, search bar, and search display controller protocols, but it is the concrete instances of this class’ sub-classes that do all the dirty work. Each one has a complete implementation of all the delegate methods necessary to fulfill the requirements.
In most cases, each delegate only needed access to the objects passed as arguments to the implemented delegate methods along with whatever data source the delegate object was in charge of. However there were instances where the lower-level delegate object needed to tell the top-level view controller to reload the table. To do that, I used a notification, that the top-level view controller listened for. It worked beautifully. There were a couple instances where the delegates needed access to something else in the top-level UIViewController. To solve that problem, perhaps inelegantly but effectively, each delegate was initialized with a pointer to the top-level object. This came in handy when it was time for one of the delegates to push another view controller onto the top level view controller’s navigation stack.
This short overview doesn’t do the topic justice, but I can report that I successfully pulled this off, and the code isn’t horrible. The basic lesson is that you can create standalone delegate objects, but you should think carefully before doing so, because it will likely be more, not less, work. In my situation, we had no choice but to do it this way. In the end, we have nice separation of responsibility divided among several distinct classes and files. The alternative might have been to put all this stuff into a single UIViewController implementation, but that would have been untenable, not to mention incredibly hard to maintain. So ultimately, while perhaps much more complex architecturally, we have something that is very extensible and I would argue more maintainable.
The real world is always about trade offs. We made some here, but the final product is a thing of beauty.
Can you show what the scope delegates were doing?
Sadly, no. The code samples in the article are sanitized from the original because the work was done for my employer. But generally speaking, the delegate objects simply implemented all the standard UITableView, UISearchBar and UISearchBarController delegate and datasource methods that you would normally implement, and they included the application-logic for fetching and managing data. The point of the article was to indicate, in fairly broad brush strokes, that such an architecture could actually be done.