假设我有一个包含UINavigationController
作为初始视图控制器的故事板。它的根视图控制器是UITableViewController
的子类,它是BasicViewController
。它有IBAction
,它连接到导航栏的右侧导航按钮
从那里我想使用故事板作为其他视图的模板,而无需创建其他故事板。假设这些视图将具有完全相同的接口,但具有SpecificViewController1
类和SpecificViewController2
类的根视图控制器,它们是BasicViewController
的子类。
除了IBAction
方法之外,这两个视图控制器将具有相同的功能和接口。
它将如下所示:
@interface BasicViewController : UITableViewController
@interface SpecificViewController1 : BasicViewController
@interface SpecificViewController2 : BasicViewController
我可以这样做吗?
我可以实例化BasicViewController
的故事板,但有根视图控制器子类SpecificViewController1
和SpecificViewController2
?
谢谢。
很好的问题 - 但不幸的是只有一个蹩脚的答案。我不相信目前可以做你提出的建议,因为UIStoryboard中没有初始化程序允许覆盖与故事板相关联的视图控制器,如初始化时故事板中的对象详细信息中所定义。初始化时,stoaryboard中的所有UI元素都链接到视图控制器中的属性。
默认情况下,它将使用storyboard定义中指定的视图控制器进行初始化。
如果您试图重新使用在故事板中创建的UI元素,它们仍然必须链接或关联到视图控制器正在使用它们的属性,以便它们能够“告诉”视图控制器有关事件的信息。
复制故事板布局并不是什么大问题,特别是如果你只需要3个视图的类似设计,但是如果你这样做,你必须确保清除所有以前的关联,否则它会在尝试时崩溃与前一个视图控制器通信。您将能够在日志输出中将它们识别为KVO错误消息。
您可以采取以下几种方法:
如果Apple提出了一种方法来做你提出的建议会很好,但是将图形元素与控制器子类预关联的问题仍然是一个问题。
祝新年快乐!好吧
从这里和那里得到答案,我想出了这个简洁的解决方案。
使用此功能创建父视图控制器。
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引用,然后继续调用“下一个”子视图控制器。
我们正在寻找的代码是:
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
}
}
正如公认的答案所述,它看起来不像是可以用故事板。
我的解决方案是使用Nib - 就像开发人员在故事板之前使用它们一样。如果你想拥有一个可重用的,可子类化的视图控制器(甚至是一个视图),我建议使用Nibs。
SubclassMyViewController *myViewController = [[SubclassMyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];
当您将所有插座连接到MyViewController.xib
中的“文件所有者”时,您没有指定Nib应该加载的类,您只需指定键值对:“此视图应连接到此实例变量名称。”调用[SubclassMyViewController alloc] initWithNibName:
时,初始化过程指定将使用哪个视图控制器来“控制”您在笔尖中创建的视图。
故事板可以实例化自定义视图控制器的不同子类,但它涉及一种稍微不正统的技术:覆盖视图控制器的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];
}
}
}
对于我的测试,我有两个TestViewController
子类:RedTestViewController
和GreenTestViewController
。每个子类都有其他属性,每个子类都覆盖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
本身,在其他场合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"];
}
请注意,setClassForStoryBoard
方法检查以确保所请求的类名确实是TestViewController的子类,以避免任何混淆。以上对BlueTestViewController
的引用是为了测试这个功能。
在instantiateViewControllerWithIdentifier之后尝试这个。
- (void)setClass:(Class)c {
object_setClass(self, c);
}
喜欢 :
SubViewController *vc = [sb instantiateViewControllerWithIdentifier:@"MainViewController"];
[vc setClass:[SubViewController class]];
虽然它不是严格意义上的子类,但您可以:
这是我写的Bloc教程的一个例子,用ViewController
子类化WhiskeyViewController
:
这允许您在故事板中创建视图控制器子类的子类。然后,您可以使用instantiateViewControllerWithIdentifier:
创建特定的子类。
这种方法有点不灵活:以后在故事板中对基类控制器的修改不会传播到子类。如果您有很多子类,那么使用其他解决方案可能会更好,但这样做会有所帮助。
Objc_setclass方法不会创建childvc的实例。但是当弹出childvc时,childvc的deinit正在调用。由于没有为childvc分配内存,app崩溃了。 Basecontroller有一个实例,而子vc没有。
如果您不太依赖于故事板,则可以为控制器创建单独的.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
}
}
可能最灵活的方式是使用可重用的视图。
(在单独的XIB文件或Container view
中创建视图,并将其添加到storyboard中的每个子类视图控制器场景)