Reputation: 1070
I have this UIViewController set up in in my storyboard, with all the outlets, views, and constraints I need. Perfect. Let's call this WatchStateController, it'll serve as an abstract parent class.
I then have this subclass of WatchStateController, called WatchStateTimeController, which will have the functionality I need for a particular state of the application.
Because I am trying to use the 1 view controller in the UIStoryboard, I'm having some problems in instantiating a WatchStateTimeController as type WatchStateTimeController - it instantiates as WatchStateController.
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];
WatchStateTimeController *timeController = (WatchStateTimeController *)[mainStoryboard instantiateViewControllerWithIdentifier:@"WatchStateController"];
This is because the "Class" field in the storyboard's Identity Inspector is set to "WatchStateController". So the question is, how do I merely change this classname set in the Identity Inspector at runtime?
NOTE: ignore why I'm trying to do this and concentrate on how. If you really must know why, you can read up on the Strategy design pattern.
Upvotes: 3
Views: 2648
Reputation: 151
One slightly dirty workaround I'm using to force storyboard to be compatible with the strategy pattern: I write a custom allocator in the base (abstract) view controller that returns an instance of the desired concrete view controller subclass, before the storyboard mechanism gets over.
For this to work, you have to tell the base class which subclass you want to instantiate.
So, in the base controller:
Class _concreteSubclass = nil;
+ (void) setConcreteSubclassToInstantiate:(Class)c {
_concreteSubclass = c;
}
+ (id)allocWithZone: (NSZone *)zone {
Class c = _concreteSubclass ?: [self class];
void *object = calloc(class_getInstanceSize(c), 1);
*(Class *)object = c;
return (id)CFBridgingRelease(object);
}
This instantiates enough memory for the ivars of the subclass too.
The view controller type of "MyViewController" known to the storyboard is just "BaseViewController"; but then, where you ask the storyboard to instantiate the view controller, you do something like this:
[BaseViewController setConcreteSubclassToInstantiate:[SomeSubclassOfBaseViewController class]];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle: nil];
SomeSubclassOfBaseViewController *vc = (SomeSubclassOfBaseViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:@"MyViewController"];
[self presentViewController:vc animated:NO completion:^{}];
The concrete view controller is instantiated and shown without a hitch.
Upvotes: 6
Reputation: 12005
Here's an example of the strategy pattern using a helper object, as I described in the comments:
@class WatchStateController;
@protocol WatchStateStrategy <NSObject>
- (void)doSomeBehaviorPolymorphically:(WatchStateController *)controller;
@end
@interface WatchStateController
// or call this a delegate or whatever makes sense.
@property (nonatomic) id <WatchStateStrategy> strategy;
@end
@implementation WatchStateController
- (void)someAction:(id)sender
{
[self.strategy doSomeBehaviorPolymorphically:self];
}
@end
@interface WatchStateTimeStrategy <WatchStateStrategy>
@end
@implementation WatchStateTimeStrategy
- (void)doSomeBehaviorPolymorphically:(WatchStateController *)controller
{
// here's one variation of the behavior
}
@end
@interface WatchStateAnotherStrategy <WatchStateStrategy>
@end
@implementation WatchStateAnotherStrategy
- (void)doSomeBehaviorPolymorphically:(WatchStateController *)controller
{
// here's another variation of the behavior
}
@end
And to set this up when you are presenting your view controller, assign the appropriate helper object (instead of attempting to change the subclass of the view controller itself):
WatchStateController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"WatchStateController"];
if (useTimeStrategy) {
viewController.strategy = [WatchStateTimeStrategy new];
} else {
viewController.strategy = [WatchStateAnotherStrategy new];
}
The advantages I see to this approach compared to subclassing the view controller:
Upvotes: 5