ios 从故事板中的外部 xib 文件加载视图
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9282365/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Load view from an external xib file in storyboard
提问by Sebastian Hoffmann
I want to use a view throughout multiple viewcontrollers in a storyboard. Thus, I thought about designing the view in an external xib so changes are reflected in every viewcontroller. But how can one load a view from a external xib in a storyboard and is it even possible? If thats not the case, what other alternatives are availble to suit the situation abouve?
我想在故事板中的多个视图控制器中使用一个视图。因此,我考虑在外部 xib 中设计视图,以便更改反映在每个视图控制器中。但是如何从故事板中的外部 xib 加载视图,甚至可能吗?如果不是这种情况,还有哪些其他选择可以适应上述情况?
回答by Suragch
My full example is here, but I will provide a summary below.
我的完整示例在这里,但我将在下面提供一个摘要。
Layout
布局
Add a .swift and .xib file each with the same name to your project. The .xib file contains your custom view layout (using auto layout constraints preferably).
将一个 .swift 和 .xib 文件添加到您的项目中,每个文件都具有相同的名称。.xib 文件包含您的自定义视图布局(最好使用自动布局约束)。
Make the swift file the xib file's owner.
将 swift 文件设为 xib 文件的所有者。
Add the following code to the .swift file and hook up the outlets and actions from the .xib file.
将以下代码添加到 .swift 文件并连接 .xib 文件中的 outlet 和 actions。
import UIKit
class ResuableCustomView: UIView {
let nibName = "ReusableCustomView"
var contentView: UIView?
@IBOutlet weak var label: UILabel!
@IBAction func buttonTap(_ sender: UIButton) {
label.text = "Hi"
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
guard let view = loadViewFromNib() else { return }
view.frame = self.bounds
self.addSubview(view)
contentView = view
}
func loadViewFromNib() -> UIView? {
let bundle = Bundle(for: type(of: self))
let nib = UINib(nibName: nibName, bundle: bundle)
return nib.instantiate(withOwner: self, options: nil).first as? UIView
}
}
Use it
用它
Use your custom view anywhere in your storyboard. Just add a UIView
and set the class name to your custom class name.
在故事板的任何位置使用自定义视图。只需添加一个UIView
并将类名设置为您的自定义类名。
回答by Ben Patch
For a while Christopher Swasey's approachwas the best approach I had found. I asked a couple of the senior devs on my team about it and one of them had the perfect solution! It satisfies every one of the concerns that Christopher Swasey so eloquently addressed and it doesn't require boilerplate subclass code(my main concern with his approach). There is one gotcha, but other than that it is fairly intuitive and easy to implement.
有一段时间Christopher Swasey 的方法是我找到的最好的方法。我询问了我团队中的几位高级开发人员,其中一位提供了完美的解决方案!它满足了 Christopher Swasey 雄辩地解决的所有问题,并且不需要样板子类代码(我对他的方法的主要关注点)。有一个问题,但除此之外,它相当直观且易于实现。
- Create a custom UIView class in a .swift file to control your xib. i.e.
MyCustomClass.swift
- Create a .xib file and style it as you want. i.e.
MyCustomClass.xib
- Set the
File's Owner
of the .xib file to be your custom class (MyCustomClass
) - GOTCHA:leave the
class
value (under theidentity Inspector
) for your custom view in the .xib file blank. So your custom view will have no specified class, but it will have a specified File's Owner. - Hook up your outlets as you normally would using the
Assistant Editor
.- NOTE: If you look at the
Connections Inspector
you will notice that your Referencing Outlets do not reference your custom class (i.e.MyCustomClass
), but rather referenceFile's Owner
. SinceFile's Owner
is specified to be your custom class, the outlets will hook up and work propery.
- NOTE: If you look at the
- Make sure your custom class has @IBDesignable before the class statement.
- Make your custom class conform to the
NibLoadable
protocol referenced below.- NOTE: If your custom class
.swift
file name is different from your.xib
file name, then set thenibName
property to be the name of your.xib
file.
- NOTE: If your custom class
- Implement
required init?(coder aDecoder: NSCoder)
andoverride init(frame: CGRect)
to callsetupFromNib()
like the example below. - Add a UIView to your desired storyboard and set the class to be your custom class name (i.e.
MyCustomClass
). - Watch IBDesignable in action as it draws your .xib in the storyboard with all of it's awe and wonder.
- 在 .swift 文件中创建自定义 UIView 类来控制您的 xib。IE
MyCustomClass.swift
- 创建一个 .xib 文件并根据需要设置样式。IE
MyCustomClass.xib
- 将
File's Owner
.xib 文件的 设置为您的自定义类 (MyCustomClass
) - GOTCHA:将.xib 文件中自定义视图的
class
值(在 下identity Inspector
)留空。因此,您的自定义视图将没有指定的类,但它将具有指定的文件所有者。 - 像通常使用
Assistant Editor
.- 注意:如果您查看 ,
Connections Inspector
您会注意到您的引用插座不引用您的自定义类(即MyCustomClass
),而是引用File's Owner
. 由于File's Owner
指定为您的自定义类,因此插座将连接并正常工作。
- 注意:如果您查看 ,
- 确保您的自定义类在 class 语句之前具有 @IBDesignable。
- 使您的自定义类符合
NibLoadable
下面引用的协议。- 注意:如果您的自定义类
.swift
文件名与您的.xib
文件名不同,则将该nibName
属性设置为您的.xib
文件名。
- 注意:如果您的自定义类
- 像下面的例子一样实现
required init?(coder aDecoder: NSCoder)
和override init(frame: CGRect)
调用setupFromNib()
。 - 将 UIView 添加到您想要的故事板并将类设置为您的自定义类名(即
MyCustomClass
)。 - 观看 IBDesignable 的实际操作,因为它在故事板中绘制您的 .xib,充满敬畏和惊奇。
Here is the protocol you will want to reference:
这是您要参考的协议:
public protocol NibLoadable {
static var nibName: String { get }
}
public extension NibLoadable where Self: UIView {
public static var nibName: String {
return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
}
public static var nib: UINib {
let bundle = Bundle(for: Self.self)
return UINib(nibName: Self.nibName, bundle: bundle)
}
func setupFromNib() {
guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
addSubview(view)
view.translatesAutoresizingMaskIntoConstraints = false
view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
}
}
And here is an example of MyCustomClass
that implements the protocol (with the .xib file being named MyCustomClass.xib
):
这是一个MyCustomClass
实现协议的例子(.xib 文件被命名MyCustomClass.xib
):
@IBDesignable
class MyCustomClass: UIView, NibLoadable {
@IBOutlet weak var myLabel: UILabel!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setupFromNib()
}
override init(frame: CGRect) {
super.init(frame: frame)
setupFromNib()
}
}
NOTE: If you miss the Gotcha and set the class
value inside your .xib file to be your custom class, then it will not draw in the storyboard and you will get a EXC_BAD_ACCESS
error when you run the app because it gets stuck in an infinite loop of trying to initialize the class from the nib using the init?(coder aDecoder: NSCoder)
method which then calls Self.nib.instantiate
and calls the init
again.
注意:如果您错过了 Gotcha 并将class
.xib 文件中的值设置为您的自定义类,那么它不会在故事板中绘制,并且EXC_BAD_ACCESS
在您运行应用程序时会出现错误,因为它陷入无限循环尝试使用init?(coder aDecoder: NSCoder)
然后调用Self.nib.instantiate
并init
再次调用的方法从笔尖初始化类。
回答by user1021430
Assuming that you've created an xib that you want to use:
假设您已经创建了一个要使用的 xib:
1) Create a custom subclass of UIView (you can go to File -> New -> File... -> Cocoa Touch Class. Make sure "Subclass of:" is "UIView").
1) 创建 UIView 的自定义子类(您可以转到 File -> New -> File... -> Cocoa Touch Class。确保“Subclass of:”为“UIView”)。
2) Add a view that's based on the xib as a subview to this view at initialization.
2)在初始化时添加一个基于xib的视图作为该视图的子视图。
In Obj-C
在 Obj-C 中
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]) {
UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
owner:self
options:nil] objectAtIndex:0];
xibView.frame = self.bounds;
xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview: xibView];
}
return self;
}
In Swift 2
在斯威夫特 2
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
xibView.frame = self.bounds
xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(xibView)
}
In Swift 3
在斯威夫特 3
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
xibView.frame = self.bounds
xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
self.addSubview(xibView)
}
3) Wherever you want to use it in your storyboard, add a UIView as you normally would, select the newly added view, go to the Identity Inspector (the third icon on the upper right that looks like a rectangle with lines in it), and enter your subclass's name in as the "Class" under "Custom Class".
3) 无论你想在故事板的任何地方使用它,像往常一样添加一个 UIView,选择新添加的视图,转到身份检查器(右上角的第三个图标,看起来像一个带有线条的矩形),并在“自定义类”下的“类”中输入子类的名称。
回答by Christopher Swasey
I've always found the "add it as a subview" solution unsatisfactory, seeing as it screws with (1) autolayout, (2) @IBInspectable
, and (3) outlets. Instead, let me introduce you to the magic of awakeAfter:
, an NSObject
method.
我一直发现“将其添加为子视图”解决方案并不令人满意,因为它与 (1) 自动布局、(2)@IBInspectable
和 (3) 插座拧在一起。相反,让我向您介绍awakeAfter:
一种NSObject
方法的神奇之处。
awakeAfter
lets you swap out the object actually woken up from a NIB/Storyboard with a different object entirely. Thatobject is then put through the hydration process, has awakeFromNib
called on it, is added as a view, etc.
awakeAfter
允许您将实际从 NIB/故事板唤醒的对象完全换成不同的对象。然后该对象经过水化过程,awakeFromNib
调用它,添加为视图等。
We can use this in a "cardboard cut-out" subclass of our view, the only purpose of which will be to load the view from the NIB and return it for use in the Storyboard. The embeddable subclass is then specified in the Storyboard view's identity inspector, rather than the original class. It doesn't actually have to be a subclass in order for this to work, but making it a subclass is what allows IB to see any IBInspectable/IBOutlet properties.
我们可以在视图的“纸板剪裁”子类中使用它,其唯一目的是从 NIB 加载视图并将其返回以在 Storyboard 中使用。然后在 Storyboard 视图的身份检查器中指定可嵌入的子类,而不是原始类。它实际上不必是子类才能使其工作,但使其成为子类允许 IB 查看任何 IBInspectable/IBOutlet 属性。
This extra boilerplate might seem suboptimal—and in a sense it is, because ideally UIStoryboard
would handle this seamlessly—but it has the advantage of leaving the original NIB and UIView
subclass completely unmodified. The role it plays is basically that of an adapter or bridge class, and is perfectly valid, design-wise, as an additional class, even if it is regrettable. On the flip side, if you prefer to be parsimonious with your classes, @BenPatch's solution works by implementing a protocol with some other minor changes. The question of which solution is better boils down to a matter of programmer style: whether one prefers object composition or multiple inheritance.
这个额外的样板可能看起来不太理想——从某种意义上说它是,因为理想情况下UIStoryboard
可以无缝地处理这个问题——但它的优点是UIView
完全不修改原始 NIB 和子类。它所扮演的角色基本上是一个适配器或桥接类,并且在设计方面作为一个附加类是完全有效的,即使它是令人遗憾的。另一方面,如果您喜欢对您的类保持简约,@BenPatch 的解决方案通过实现具有其他一些细微更改的协议来工作。哪种解决方案更好的问题归结为程序员风格的问题:是否更喜欢对象组合或多重继承。
Note: the class set on the view in the NIB file remains the same. The embeddable subclass is onlyused in the storyboard. The subclass can't be used to instantiate the view in code, so it shouldn't have any additional logic, itself. It should onlycontain the awakeAfter
hook.
注意:NIB 文件中视图上设置的类保持不变。可嵌入子类仅用于故事板。子类不能用于在代码中实例化视图,因此它本身不应该有任何额外的逻辑。它应该只包含awakeAfter
钩子。
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
}
}
?? The one significant drawback here is that if you define width, height, or aspect ratio constraints in the storyboard that don't relate to another view then they have to be copied over manually. Constraints that relate two views are installed on the nearest common ancestor, and views are woken from the storyboard from the inside-out, so by the time those constraints are hydrated on the superview the swap has already occurred. Constraints that only involve the view in question are installed directly on that view, and thus get tossed when the swap occurs unless they are copied.
?? 这里的一个显着缺点是,如果您在故事板中定义与另一个视图无关的宽度、高度或纵横比约束,则必须手动复制它们。将两个视图相关的约束安装在最近的共同祖先上,并且视图从故事板中从内到外唤醒,因此当这些约束在父视图上水合时,交换已经发生。仅涉及相关视图的约束直接安装在该视图上,因此在发生交换时会被丢弃,除非它们被复制。
Note that what is happening here is constraints installed on the view in the storyboardare copied to the newly instantiated view, which may already have constraints of its own, defined in its nib file. Those are unaffected.
请注意,这里发生的是故事板中视图上安装的约束被复制到新实例化的视图中,该视图可能已经有自己的约束,在其 nib 文件中定义。那些不受影响。
class MyCustomEmbeddableView: MyCustomView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!
for constraint in constraints {
if constraint.secondItem != nil {
newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
} else {
newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
}
}
return newView as Any
}
}
instantiateViewFromNib
is a type-safe extension to UIView
. All it does is loop through the NIB's objects until it finds one that matches the type. Note that the generic type is the returnvalue, so the type has to be specified at the call site.
instantiateViewFromNib
是UIView
. 它所做的只是遍历 NIB 的对象,直到找到与类型匹配的对象。请注意,泛型类型是返回值,因此必须在调用站点指定类型。
extension UIView {
public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
if let objects = bundle.loadNibNamed(nibName, owner: nil) {
for object in objects {
if let object = object as? T {
return object
}
}
}
return nil
}
}
回答by Ben G
Best solution currently is to just use a custom view controller with its view defined in a xib, and simply delete the "view" property that Xcode creates inside the storyboard when adding the view controller to it (don't forget to set the name of the custom class though).
目前最好的解决方案是只使用自定义视图控制器,其视图定义在 xib 中,并在添加视图控制器时简单地删除 Xcode 在情节提要中创建的“视图”属性(不要忘记设置名称自定义类)。
This will make the runtime automatically look for the xib and load it. You can use this trick for any kind of container views, or content view.
这将使运行时自动查找 xib 并加载它。您可以将此技巧用于任何类型的容器视图或内容视图。
回答by Micha? Ziobro
I think about alternative
for using XIB views
to be using View Controller
in separate storyboard.
我想alternative
利用XIB views
被使用View Controller
在不同的故事板。
Then in main storyboard in place of custom view use container view
with Embed Segue
and have StoryboardReference
to this custom view controllerwhich view should be placed inside other view in main storyboard.
然后,在代替的自定义视图使用主要故事板container view
与Embed Segue
和具有StoryboardReference
该自定义视图控制器哪些视图应放置在主情节串连图板的其他视图内。
Then we can set up delegationand communication between this embed ViewController and main view controller through prepare for segue. This approach is differentthen displaying UIView, but much simpler and more efficiently (from programming perspective) can be utilised to achieve the same goal, i.e. have reusable custom view that is visible in main storyboard
然后我们可以通过prepare for segue在这个嵌入的 ViewController 和主视图控制器之间设置委托和通信。这种方法与显示 UIView不同,但可以利用更简单和更有效(从编程角度)来实现相同的目标,即具有在主故事板中可见的可重用自定义视图
The additional advantage is that you can implement you logic in CustomViewController class and there set up all delegation and view preparation without creating separate (harder to find in project) controller classes, and without placing boilerplate code in main UIViewController using Component. I think this is good for reusable components ex. Music Player component (widget like) that is embeddable in other views.
另一个优点是您可以在 CustomViewController 类中实现逻辑,并且无需创建单独的(在项目中更难找到)控制器类,并且无需使用 Component 在主 UIViewController 中放置样板代码即可设置所有委托和视图准备。我认为这对可重用组件有好处,例如。可嵌入到其他视图中的音乐播放器组件(类似小部件)。
回答by Ingun???
Although the top most popular answers works fine, they are wrong. They all use File's owner
as connection between class's outlets and UI components. File's owner
is supposed to be used only for top-level objects not UIView
s. Check out Apple developer document.
Having UIView as File's owner
leads to these undesirable consequences.
尽管最受欢迎的答案效果很好,但它们是错误的。它们都File's owner
用作类的插座和 UI 组件之间的连接。File's owner
应该仅用于顶级对象而不是UIView
s。查看Apple 开发人员文档。将 UIView 作为File's owner
导致这些不良后果。
- You are forced to use the
contentView
. You are supposed to useself
. It's not only ugly, but also structurally wrong because data structure better convey it's UI structure. UsingcontentView
is like the opposite of declarative UI. - You can only have one UIView per Xib. An Xib is supposed to have multiple UIViews.
- 您被迫使用
contentView
. 你应该使用self
. 它不仅丑陋,而且在结构上也是错误的,因为数据结构更好地传达了它的 UI 结构。使用contentView
就像声明式 UI 的对立面。 - 每个 Xib 只能有一个 UIView。Xib 应该有多个 UIViews。
There's elegant way to do it without using File's owner
. Please check this blog post. It explains how to do it the right way.
有一种优雅的方法可以不使用File's owner
. 请查看此博客文章。它解释了如何以正确的方式做到这一点。
回答by Ariel Bogdziewicz
Solution for Objective-C according to steps described in Ben Patch's response.
根据Ben Patch 的响应中描述的步骤,Objective-C 的解决方案。
Use extension for UIView:
使用 UIView 扩展:
@implementation UIView (NibLoadable)
- (UIView*)loadFromNib
{
UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
xibView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:xibView];
[xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
[xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
[xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
[xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
return xibView;
}
@end
Create files MyView.h
, MyView.m
and MyView.xib
.
创建文件MyView.h
,MyView.m
和MyView.xib
.
First prepare your MyView.xib
as Ben Patch's responsesays so set class MyView
for File's owner instead of main view inside this XIB.
首先准备你的,MyView.xib
因为Ben Patch 的回应说所以MyView
为 File 的所有者设置类,而不是这个 XIB 中的主视图。
MyView.h
:
MyView.h
:
#import <UIKit/UIKit.h>
IB_DESIGNABLE @interface MyView : UIView
@property (nonatomic, weak) IBOutlet UIView* someSubview;
@end
MyView.m
:
MyView.m
:
#import "MyView.h"
#import "UIView+NibLoadable.h"
@implementation MyView
#pragma mark - Initializers
- (id)init
{
self = [super init];
if (self) {
[self loadFromNib];
[self internalInit];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self loadFromNib];
[self internalInit];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self loadFromNib];
}
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
[self internalInit];
}
- (void)internalInit
{
// Custom initialization.
}
@end
And later just create your view programatically:
稍后只需以编程方式创建您的视图:
MyView* view = [[MyView alloc] init];
Warning!Preview of this view will not be shown in Storyboard if you use WatchKit Extension because of this bug in Xcode >= 9.2: https://forums.developer.apple.com/thread/95616
警告!如果您使用 WatchKit 扩展,则此视图的预览将不会显示在 Storyboard 中,因为 Xcode >= 9.2 中的此错误:https://forums.developer.apple.com/thread/95616
回答by barefeettom
Here's the answer you've wanted all along. You can just create your CustomView
class, have the master instance of it in a xib with all the subviews and outlets. Then you can apply that class to any instances in your storyboards or other xibs.
这就是你一直想要的答案。您可以创建您的CustomView
类,在包含所有子视图和插座的 xib 中拥有它的主实例。然后,您可以将该类应用于故事板或其他 xib 中的任何实例。
No need to fiddle with File's Owner, or connect outlets to a proxy or modify the xib in a peculiar way, or add an instance of your custom view as a subview of itself.
无需摆弄文件所有者,或将插座连接到代理或以特殊方式修改 xib,或添加自定义视图的实例作为其自身的子视图。
Just do this:
只需这样做:
- Import BFWControls framework
- Change your superclass from
UIView
toNibView
(or fromUITableViewCell
toNibTableViewCell
)
- 导入 BFWControls 框架
- 从更改超
UIView
至NibView
(或UITableViewCell
到NibTableViewCell
)
That's it!
就是这样!
It even works with IBDesignable to refer your custom view (including the subviews from the xib) at design time in the storyboard.
它甚至可以与 IBDesignable 一起在设计时在故事板中引用您的自定义视图(包括来自 xib 的子视图)。
You can read more about it here: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155
你可以阅读更多关于它在这里: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155
And you can get the open source BFWControls framework here: https://github.com/BareFeetWare/BFWControls
您可以在此处获取开源 BFWControls 框架:https: //github.com/BareFeetWare/BFWControls
And here's a simple extract of the NibReplaceable
code that drives it, in case you're curious:?https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4
这NibReplaceable
是驱动它的代码的简单摘录,以防你好奇:?https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4
Tom
汤姆
回答by otusweb
This solution can be used even if your class does not have the same name as the XIB. For example, if you have a base view controller class controllerA which has a XIB name controllerA.xib and you subclassed this with controllerB and want to create an instance of controllerB in a storyboard, then you can:
即使您的类与 XIB 的名称不同,也可以使用此解决方案。例如,如果您有一个基本视图控制器类 controllerA,它有一个 XIB 名称 controllerA.xib,并且您将其子类化为 controllerB 并想在故事板中创建 controllerB 的实例,那么您可以:
- create the view controller in the storyboard
- set the class of the controller to the controllerB
- delete the view of the controllerB in the storyboard
- override load view in controllerA to:
- 在故事板中创建视图控制器
- 将控制器的类设置为 controllerB
- 删除storyboard中controllerB的view
- 将控制器 A 中的加载视图覆盖为:
*
*
- (void) loadView
{
//according to the documentation, if a nibName was passed in initWithNibName or
//this controller was created from a storyboard (and the controller has a view), then nibname will be set
//else it will be nil
if (self.nibName)
{
//a nib was specified, respect that
[super loadView];
}
else
{
//if no nib name, first try a nib which would have the same name as the class
//if that fails, force to load from the base class nib
//this is convenient for including a subclass of this controller
//in a storyboard
NSString *className = NSStringFromClass([self class]);
NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"];
UINib *nib ;
if (pathToNIB)
{
nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
}
else
{
//force to load from nib so that all subclass will have the correct xib
//this is convenient for including a subclass
//in a storyboard
nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
}
self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
}
}