如何使用单个故事板uiviewcontroller为多个子类

ver*_*rdy 112 cocoa-touch objective-c storyboard uiviewcontroller ios5

假设我有一个包含UINavigationController初始视图控制器的故事板.它的根视图控制器是子类UITableViewController,它是BasicViewController.它已IBAction连接到导航栏的右侧导航按钮

从那里我想使用故事板作为其他视图的模板,而无需创建其他故事板.假设这些视图具有完全相同的接口,但具有类的根视图控制器,SpecificViewController1并且SpecificViewController2是其子类BasicViewController.
除了IBAction方法之外,这两个视图控制器将具有相同的功能和接口.
它将如下所示:

@interface BasicViewController : UITableViewController

@interface SpecificViewController1 : BasicViewController

@interface SpecificViewController2 : BasicViewController
Run Code Online (Sandbox Code Playgroud)

我可以这样做吗?
我可以实例化故事板BasicViewController但是具有根视图控制器的子类SpecificViewController1SpecificViewController2

谢谢.

Coc*_*aEv 57

很好的问题 - 但不幸的是只有一个蹩脚的答案.我不相信目前可以做你提出的建议,因为UIStoryboard中没有初始化程序允许覆盖与故事板相关联的视图控制器,如初始化时故事板中的对象详细信息中所定义.初始化时,stoaryboard中的所有UI元素都链接到视图控制器中的属性.

默认情况下,它将使用storyboard定义中指定的视图控制器进行初始化.

如果您试图重新使用在故事板中创建的UI元素,它们仍然必须链接或关联到视图控制器正在使用它们的属性,以便它们能够"告诉"视图控制器有关事件的信息.

复制故事板布局并不是什么大问题,特别是如果你只需要3个视图的类似设计,但是如果你这样做,你必须确保清除所有以前的关联,否则它会在尝试时崩溃与前一个视图控制器通信.您将能够在日志输出中将它们识别为KVO错误消息.

您可以采取以下几种方法:

  • 将UI元素存储在UIView中 - 在xib文件中,并从基类中实例化它,并将其作为子视图添加到主视图中,通常是self.view.然后,您只需使用故事板布局,基本上空白的视图控制器在故事板中保留它们的位置,但分配给它们的是正确的视图控制器子类.由于他们会从基地继承,他们会得到那个观点.

  • 在代码中创建布局并从基本视图控制器安装它.显然,这种方法违背了使用故事板的目的,但可能是你的情况.如果您有应用程序的其他部分可以从故事板方法中受益,那么可以在适当的时候在这里和那里进行偏离.在这种情况下,如上所述,您只需使用分配了子类的银行视图控制器,并让基本视图控制器安装UI.

如果Apple提出了一种方法来做你提出的建议会很好,但是将图形元素与控制器子类预关联的问题仍然是一个问题.

祝新年快乐!好吧

  • 太糟糕了:(猜猜我必须复制并粘贴相同的视图控制器并将其类更改为变通方法. (2认同)
  • 还有另一种方法:在不同的委托中指定自定义逻辑,然后在prepareForSegue中分配正确的委托。这样,您在情节提要中创建了1个UIViewController + 1个UIViewController,但是您有多个实现版本。 (2认同)

Jiř*_*lka 44

我们正在寻找的代码是:

object_setClass(AnyObject!, AnyClass!)
Run Code Online (Sandbox Code Playgroud)

在Storyboard中 - >添加UIViewController为它提供一个ParentVC类名.

class ParentVC: UIViewController {

    var type: Int?

    override func awakeFromNib() {

        if type = 0 {

            object_setClass(self, ChildVC1.self)
        }
        if type = 1 {

            object_setClass(self, ChildVC2.self)
        }  
    }

    override func viewDidLoad() {   }
}

class ChildVC1: ParentVC {

    override func viewDidLoad() {
        super.viewDidLoad()

        println(type)
        // Console prints out 0
    }
}

class ChildVC2: ParentVC {

    override func viewDidLoad() {
        super.viewDidLoad()

        println(type)
        // Console prints out 1
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 使用时要非常小心!通常这根本不应该使用......这只是改变了给定指针的isa指针,并没有重新分配内存以适应例如不同的属性.一个指标是指向"self"的指针不会改变.因此,在`object_setClass`之后检查对象(例如读取_ivar/property值)会导致崩溃. (9认同)
  • 谢谢,它只是工作,例如:`class func instantiate() - > SubClass {let instance =(name:"Main",bundle:nil).instantiateViewControllerWithIdentifier("SuperClass")as?SuperClass)!object_setClass(instance,SubClass.self)返回(实例为?SubClass)!}` (4认同)
  • 这应该是公认的解决方案! (3认同)
  • 我不确定我是否理解这应该如何工作.父母将其班级设定为孩子的班级?那你怎么能有多个孩子?! (3认同)
  • 先生,你做了我的一天 (2认同)
  • 好的,让我更详细地解释一下:我们想要实现什么?我们想要子类化我们的 ParentViewController 以便我们可以将它的 Storyboard 用于更多类。因此,完成这一切的魔法线在我的解决方案中突出显示,并且必须在 ParentVC 中的awakeFromNib 中使用。然后发生的是它使用新设置的 ChildVC1 中的所有方法,它成为子类。如果您想将其用于更多 ChildVC?只需在awakeFromNib 中执行您的逻辑.. if (type = a) { object_setClass(self, ChildVC1.self) } else { object_setClass(self.ChildVC2.self) } 祝你好运。 (2认同)
  • "类型"在哪里设置?用户定义的运行时属性?还是在代码中? (2认同)
  • 在设置类型之前,不会调用awakeFromNib.可能需要将类型检查登录名移动到viewDidLoad(). (2认同)

kga*_*dis 14

正如公认的答案所述,它看起来不像是可以用故事板.

我的解决方案是使用Nib - 就像开发人员在故事板之前使用它们一样.如果你想拥有一个可重用的,可子类化的视图控制器(甚至是一个视图),我建议使用Nibs.

SubclassMyViewController *myViewController = [[SubclassMyViewController alloc] initWithNibName:@"MyViewController" bundle:nil]; 
Run Code Online (Sandbox Code Playgroud)

当您将所有出口连接到"文件所有者"时,MyViewController.xib您没有指定Nib应该加载的类,您只需指定键值对:" 此视图应连接到此实例变量名称." 调用[SubclassMyViewController alloc] initWithNibName:初始化过程时,指定将使用哪个视图控制器来" 控制 "您在笔尖中创建的视图.


pba*_*sdf 9

故事板可以实例化自定义视图控制器的不同子类,但它涉及一种稍微不正统的技术:覆盖alloc视图控制器的方法.创建自定义视图控制器时,重写的alloc方法实际上返回alloc在子类上运行的结果.

我应该在答案前加上附带条件,虽然我已经在各种场景中对其进行了测试并且没有收到任何错误,但我无法确保它能够应对更复杂的设置(但我认为没有理由说它不应该工作) .此外,我还没有使用这种方法提交任何应用程序,因此有可能被Apple的审核流程拒绝(尽管我再也看不出它为什么应该这样做).

出于演示目的,我有一个UIViewController被调用的子类TestViewController,它有一个UILabel IBOutlet和一个IBAction.在我的故事板中,我添加了一个视图控制器并修改了它的类TestViewController,并将IBOutlet连接到UILabel,将IBAction连接到UIButton.我通过前面的viewController上的UIButton触发的模态segue呈现TestViewController.

故事板图像

为了控制实例化哪个类,我添加了一个静态变量和相关的类方法,因此获取/设置要使用的子类(我想可以采用其他方法来确定要实例化哪个子类):

TestViewController.m:

#import "TestViewController.h"

@interface TestViewController ()
@end

@implementation TestViewController

static NSString *_classForStoryboard;

+(NSString *)classForStoryboard {
    return [_classForStoryboard copy];
}

+(void)setClassForStoryBoard:(NSString *)classString {
    if ([NSClassFromString(classString) isSubclassOfClass:[self class]]) {
        _classForStoryboard = [classString copy];
    } else {
        NSLog(@"Warning: %@ is not a subclass of %@, reverting to base class", classString, NSStringFromClass([self class]));
        _classForStoryboard = nil;
    }
}

+(instancetype)alloc {
    if (_classForStoryboard == nil) {
        return [super alloc];
    } else {
        if (NSClassFromString(_classForStoryboard) != [self class]) {
            TestViewController *subclassedVC = [NSClassFromString(_classForStoryboard) alloc];
            return subclassedVC;
        } else {
            return [super alloc];
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

对于我的测试,我有两个子类TestViewController:RedTestViewControllerGreenTestViewController.每个子类都有其他属性,每个子类都会viewDidLoad更改视图的背景颜色并更新UILabel IBOutlet的文本:

RedTestViewController.m:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.view.backgroundColor = [UIColor redColor];
    self.testLabel.text = @"Set by RedTestVC";
}
Run Code Online (Sandbox Code Playgroud)

GreenTestViewController.m:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor greenColor];
    self.testLabel.text = @"Set by GreenTestVC";
}
Run Code Online (Sandbox Code Playgroud)

在某些情况下,我可能想要TestViewController在其他场合RedTestViewController或其他情况下实例化自己GreenTestViewController.在上面的视图控制器中,我是随机执行的,如下所示:

NSInteger vcIndex = arc4random_uniform(4);
if (vcIndex == 0) {
    NSLog(@"Chose TestVC");
    [TestViewController setClassForStoryBoard:@"TestViewController"];
} else if (vcIndex == 1) {
    NSLog(@"Chose RedVC");
    [TestViewController setClassForStoryBoard:@"RedTestViewController"];
} else if (vcIndex == 2) {
    NSLog(@"Chose BlueVC");
    [TestViewController setClassForStoryBoard:@"BlueTestViewController"];
} else {
    NSLog(@"Chose GreenVC");
    [TestViewController setClassForStoryBoard:@"GreenTestViewController"];
}
Run Code Online (Sandbox Code Playgroud)

请注意,该setClassForStoryBoard方法检查以确保所请求的类名确实是TestViewController的子类,以避免任何混淆.上面的参考BlueTestViewController是用来测试这个功能.


小智 6

在instantiateViewControllerWithIdentifier之后尝试这个.

- (void)setClass:(Class)c {
    object_setClass(self, c);
}
Run Code Online (Sandbox Code Playgroud)

喜欢 :

SubViewController *vc = [sb instantiateViewControllerWithIdentifier:@"MainViewController"];
[vc setClass:[SubViewController class]];
Run Code Online (Sandbox Code Playgroud)

  • 如果您使用子类中的实例变量,会发生什么?我猜是崩溃了,因为没有足够的内存来分配它.在我的测试中,我一直得到`EXC_BAD_ACCESS`,所以不推荐这个. (4认同)

Ada*_*ski 6

特别基于nickgzzjrJi?í Zahálka 的回答以及CocoaBob的第二个回答下的评论,我准备了一个简短的通用方法,正是 OP 所需要的。您只需要检查故事板名称和视图控制器故事板 ID

class func instantiate<T: BasicViewController>(as _: T.Type) -> T? {
        let storyboard = UIStoryboard(name: "StoryboardName", bundle: nil)
        guard let instance = storyboard.instantiateViewController(withIdentifier: "Identifier") as? BasicViewController else {
            return nil
        }
        object_setClass(instance, T.self)
        return instance as? T
    }
Run Code Online (Sandbox Code Playgroud)

添加了可选项以避免强制解包(swiftlint 警告),但方法返回正确的对象。

另外:在从强制转换的对象中读取它们之前,您需要初始化仅存在于子类中的属性(如果子类具有这些属性而BasicViewController没有)。这些属性不会被自动初始化,并且在初始化之前尝试读取它们会导致崩溃。因为它们在强制转换中起作用,所以很可能即使是弱变量也不会被设置为 nil(将包含垃圾)。


Aar*_*ger 5

尽管它不是严格的子类,但您可以:

  1. option-在文档大纲中拖动基类视图控制器以进行复制
  2. 将新的视图控制器副本移动到情节提要上的单独位置
  3. 在Identity Inspector中将Class更改为子类视图控制器

这是我编写的Bloc教程的一个示例,ViewController使用WhiskeyViewController以下方法进行子类化:

以上三个步骤的动画

这使您可以在情节提要中创建视图控制器子类的子类。然后,您可以instantiateViewControllerWithIdentifier:用来创建特定的子类。

这种方法有点不灵活:在情节提要中对基类控制器进行的后续修改不会传播到子类。如果您有很多子类,则使用其他解决方案中的一种可能会更好,但是这样做会很困难。

  • 这不是一个子类伴侣,这只是复制一个ViewController。 (10认同)
  • 我认为您没有子类化的概念。 (5认同)
  • 如果“情节提要中对基类控制器的后来修改没有传播到子类”,则不称为“子类”。它是复制并粘贴。 (5认同)