【问题标题】:How to change a UIViewController class name in a UIStoryboard at runtime如何在运行时更改 UIStoryboard 中的 UIViewController 类名
【发布时间】:2013-06-27 03:05:06
【问题描述】:

我在我的故事板中设置了这个 UIViewController,其中包含我需要的所有插座、视图和约束。完美的。我们称之为 WatchStateController,它将作为一个抽象父类。

然后,我有了 WatchStateController 的这个子类,称为 WatchStateTimeController,它将具有应用程序特定状态所需的功能。

因为我试图在 UIStoryboard 中使用 1 个视图控制器,所以在将 WatchStateTimeController 实例化为 WatchStateTimeController 类型时遇到了一些问题 - 它实例化为 WatchStateController。

UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"MainStoryboard" bundle:nil];

WatchStateTimeController *timeController = (WatchStateTimeController *)[mainStoryboard instantiateViewControllerWithIdentifier:@"WatchStateController"];

这是因为情节提要的 Identity Inspector 中的“Class”字段设置为“WatchStateController”。所以问题是,我如何在运行时更改 Identity Inspector 中设置的这个类名?

注意:忽略为什么我正在尝试这样做并专注于如何。如果您真的必须知道原因,可以阅读策略设计模式。

【问题讨论】:

  • 考虑改变你对策略设计模式的解释以更好地适应故事板给你的应用程序结构的可能性:不要使用继承来改变视图控制器的行为,而是使用聚合.换句话说,在WatchStateController 上有一个属性,它引用了某个基类或协议类型的另一个对象,它可以作为一种委托提供所需的行为。
  • 谢谢。我对聚合和组合非常熟悉——我 95% 的时间都在使用它。不幸的是,在这里使用它没有意义,它会导致重复工作,复制和粘贴相同的代码,这是我想要避免的。冗余是敌人。如果这是使用情节提要的限制,那么您是对的,必须找到模式的变体。问题是,在使用情节提要时,状态/策略模式是否完全兼容?还是我应该放弃并用一堆 IF/ELSE 语句乱扔代码(策略模式实际上是为了避免什么)
  • 也许我读错了或者太快了,但是您包含的情节提要中显示 watchstatecontroller 的图像,并且您想要更改它...不应该是 WatchStateTimeController,因为您将其子类化,这是您想要的视图子类的?也许我可以使用一个更简单的描述......就像你有一个视图 a 的类,你将视图 b 的类 a 子类化,但你的问题是视图 b 正在与 a 类一起运行?
  • @PostCodeism 我建议的替代方案不应该导致比子类化视图控制器更多的重复代码/逻辑。您将如何确定在特定情况下使用 WatchStateController 的哪个子类?无需更改控制器的子类,相同的代码只需将某个辅助对象的不同子类分配给控制器上的属性即可。 WatchStateController 中不需要条件逻辑。
  • 好吧,对不起,如果我说的很明显,但你回答我说我之前问的是正确的,所以显然我明白我的下一个问题是如果你将它子类化,为什么你不能只使用从您发布的显示 WatchStateController 的情节提要图像上显示的类选择中下拉。如果您将 a 子类化为 b 类,那么您应该能够使用下拉菜单并为视图 b 选择 b 类。

标签: iphone ios uiviewcontroller storyboard


【解决方案1】:

我用来强制情节提要与策略模式兼容的一个稍微肮脏的解决方法:我在基本(抽象)视图控制器中编写了一个自定义分配器,它在情节提要之前返回所需的具体视图控制器子类的实例机制结束。

为此,您必须告诉基类您要实例化哪个子类。

所以,在基本控制器中:

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);
}

这也为子类的 ivars 实例化了足够的内存。

情节提要已知的“MyViewController”视图控制器类型只是“BaseViewController”;但是,当您要求故事板实例化视图控制器时,您可以执行以下操作:

[BaseViewController setConcreteSubclassToInstantiate:[SomeSubclassOfBaseViewController class]];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle: nil];
SomeSubclassOfBaseViewController *vc = (SomeSubclassOfBaseViewController *)[mainStoryboard instantiateViewControllerWithIdentifier:@"MyViewController"];
[self presentViewController:vc animated:NO completion:^{}];

具体的视图控制器被实例化并顺利显示。

【讨论】:

  • 这似乎更符合我想要做的。我会测试一下。谢谢:)
【解决方案2】:

这是一个使用辅助对象的策略模式示例,正如我在 cmets 中所描述的:

@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

要在展示视图控制器时进行设置,请分配适当的辅助对象(而不是尝试更改视图控制器本身的子类):

WatchStateController *viewController = [storyboard instantiateViewControllerWithIdentifier:@"WatchStateController"];
if (useTimeStrategy) {
    viewController.strategy = [WatchStateTimeStrategy new];
} else {
    viewController.strategy = [WatchStateAnotherStrategy new];
}

与子类化视图控制器相比,我看到这种方法的优势:

  • 更贴合SOLID principles,尤其是单一职责原则、开放/封闭原则等。
  • 小型、专注的帮助类,可能很少或没有 UI 依赖项,具体取决于它们需要做什么,如果您打算编写测试,则可以更轻松地进行单元测试
  • 它更紧密地遵循 iOS 中已有的设计模式和结构模式(使用委托,并让故事板/xib 以正常方式实例化视图控制器)
  • 从视图控制器中删除逻辑。在 iOS 中,很容易得到一个包含太多逻辑的大型视图控制器;我认为我们应该一直寻找机会来改进这一点

【讨论】:

  • 这偏离了最初的问题,但我确实喜欢它的发展方向。我喜欢讨论软件模式以及我们如何在 Objective-C 中实现它们。有时我们需要重塑我们对 Xcode 的思考。但我对策略模式的理解与本书《iOS 的 Pro Objective-C 设计模式》中详述的类似。一个 CONTEXT 类,它“有一个”CONCRETE 类,它“是一种”抽象类。篇幅有限,见第19章示例:books.google.ca/…
  • 使用与您给出的示例类似的协议可能是可行的解决方案,并且与我之前的解决方案类似,但管理起来很复杂。但这不是策略模式——您仍然面临管理复杂的 IF/ELSE 语句以维护逻辑的问题。这个问题的解决方案是策略模式甚至存在的具体原因。阅读我在链接中提供的示例,并思考我们如何在 UIViewControllers 和 UIStoryboards 的约束下实现它。如果不可能,那很好。我将不得不重新实现协议解决方案:)
  • @esker 的解决方案如何需要 if/else 的逻辑?指定的协议仅封装动态行为。也可以有一个抽象类提供一些通用实现,并继承它的具体策略(尽管在这种情况下为什么不将通用 impl 放在您的 WatchStateController 中,并为其余部分使用协议)。至于策略初始化的最后一点if/else,我会使用工厂方法封装-instantiateViewControllerWithIdentifier:&vc.strategy设置,如果使用segues切换逻辑,则设置为-performSegueWithIdentifier:sender:
  • @qix 你没看到if (useTimeStrategy)的代码...?策略模式应该取消所有条件来决定要实例化哪个类。这就是它的全部意义所在。我知道这不是很常见,所以不是很多人理解它。但这是最适合我想要实现的模式
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-20
  • 1970-01-01
  • 2013-08-09
  • 2011-02-15
  • 2020-09-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多