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

问题描述 投票:106回答:10

假设我有一个包含UINavigationController作为初始视图控制器的故事板。它的根视图控制器是UITableViewController的子类,它是BasicViewController。它有IBAction,它连接到导航栏的右侧导航按钮 从那里我想使用故事板作为其他视图的模板,而无需创建其他故事板。假设这些视图将具有完全相同的接口,但具有SpecificViewController1类和SpecificViewController2类的根视图控制器,它们是BasicViewController的子类。 除了IBAction方法之外,这两个视图控制器将具有相同的功能和接口。 它将如下所示:

@interface BasicViewController : UITableViewController

@interface SpecificViewController1 : BasicViewController

@interface SpecificViewController2 : BasicViewController

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

objective-c cocoa-touch ios5 uiviewcontroller storyboard
10个回答
57
投票

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

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

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

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

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

  • 将UI元素存储在UIView中 - 在xib文件中,并从基类中实例化它,并将其作为子视图添加到主视图中,通常是self.view。然后,您只需使用故事板布局,基本上空白的视图控制器在故事板中保留它们的位置,但分配给它们的是正确的视图控制器子类。由于他们会从基地继承,他们会得到那个观点。
  • 在代码中创建布局并从基本视图控制器安装它。显然,这种方法违背了使用故事板的目的,但可能是你的情况。如果您有应用程序的其他部分可以从故事板方法中受益,那么可以在适当的时候在这里和那里进行偏离。在这种情况下,如上所述,您只需使用已分配子类的银行视图控制器,并让基本视图控制器安装UI。

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

祝新年快乐!好吧


0
投票

从这里和那里得到答案,我想出了这个简洁的解决方案。

使用此功能创建父视图控制器。

class ParentViewController: UIViewController {


    func convert<T: ParentViewController>(to _: T.Type) {

        object_setClass(self, T.self)

    }

}

这允许编译器确保子视图控制器继承父视图控制器。

然后,只要你想使用子类来转换到这个控制器,你就可以:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    super.prepare(for: segue, sender: sender)

    if let parentViewController = segue.destination as? ParentViewController {
        ParentViewController.convert(to: ChildViewController.self)
    }

}

很酷的部分是你可以为自己添加一个storyboard引用,然后继续调用“下一个”子视图控制器。


44
投票

我们正在寻找的代码是:

object_setClass(AnyObject!, AnyClass!)

在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
    }
}

12
投票

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

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

SubclassMyViewController *myViewController = [[SubclassMyViewController alloc] initWithNibName:@"MyViewController" bundle:nil]; 

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


9
投票

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

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

出于演示目的,我有一个名为UIViewControllerTestViewController子类,它有一个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];
        }
    }
}

对于我的测试,我有两个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";
}

GreenTestViewController.m:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor greenColor];
    self.testLabel.text = @"Set by GreenTestVC";
}

在某些情况下,我可能想要实例化TestViewController本身,在其他场合RedTestViewControllerGreenTestViewController。在上面的视图控制器中,我是随机执行的,如下所示:

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"];
}

请注意,setClassForStoryBoard方法检查以确保所请求的类名确实是TestViewController的子类,以避免任何混淆。以上对BlueTestViewController的引用是为了测试这个功能。


6
投票

在instantiateViewControllerWithIdentifier之后尝试这个。

- (void)setClass:(Class)c {
    object_setClass(self, c);
}

喜欢 :

SubViewController *vc = [sb instantiateViewControllerWithIdentifier:@"MainViewController"];
[vc setClass:[SubViewController class]];

5
投票

虽然它不是严格意义上的子类,但您可以:

  1. 选项 - 拖动文档大纲中的基类视图控制器以进行复制
  2. 将新视图控制器副本移动到故事板上的单独位置
  3. 将Class更改为Identity Inspector中的子类视图控制器

这是我写的Bloc教程的一个例子,用ViewController子类化WhiskeyViewController

这允许您在故事板中创建视图控制器子类的子类。然后,您可以使用instantiateViewControllerWithIdentifier:创建特定的子类。

这种方法有点不灵活:以后在故事板中对基类控制器的修改不会传播到子类。如果您有很多子类,那么使用其他解决方案可能会更好,但这样做会有所帮助。


2
投票

Objc_setclass方法不会创建childvc的实例。但是当弹出childvc时,childvc的deinit正在调用。由于没有为childvc分配内存,app崩溃了。 Basecontroller有一个实例,而子vc没有。


1
投票

如果您不太依赖于故事板,则可以为控制器创建单独的.xib文件。

将相应的文件所有者和出口设置为MainViewController并覆盖主VC中的init(nibName:bundle:),以便其子项可以访问相同的Nib及其出口。

您的代码应如下所示:

class MainViewController: UIViewController {
    @IBOutlet weak var button: UIButton!

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: "MainViewController", bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        button.tintColor = .red
    }
}

您的Child VC将能够重用其父级的笔尖:

class ChildViewController: MainViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        button.tintColor = .blue
    }
}

0
投票

可能最灵活的方式是使用可重用的视图。

(在单独的XIB文件或Container view中创建视图,并将其添加到storyboard中的每个子类视图控制器场景)

© www.soinside.com 2019 - 2024. All rights reserved.