从故事板中的外部xib文件加载视图

我想在故事板中的多个视图控制器中使用视图。 因此,我考虑在外部xib中devise视图,以便在每个视图控制器中反映更改。 但是如何在故事板中加载一个外部xib的视图,甚至有可能? 如果不是这样的话,还有什么其他的select可以适应这种情况呢?

我的完整例子在这里 ,但我会在下面提供一个总结。

布局

将.swift和.xib文件分别添加到您的项目中。 .xib文件包含您的自定义视图布局(最好使用自动布局约束)。

将swift文件作为xib文件的所有者。

在这里输入图像说明

将下面的代码添加到.swift文件中,并将.xib文件中的出口和动作连接起来。

import UIKit class ResuableCustomView: UIView { @IBOutlet var view: UIView! @IBOutlet weak var label: UILabel! @IBAction func buttonTap(sender: UIButton) { label.text = "Hi" } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) NSBundle.mainBundle().loadNibNamed("ReusableCustomView", owner: self, options: nil)[0] as! UIView self.addSubview(view) view.frame = self.bounds } } 

用它

在故事板的任何位置使用自定义视图。 只需添加一个UIView ,并将类名称设置为您的自定义类名。

在这里输入图像说明

假设你已经创build了一个你想使用的xib:

1)创build一个UIView的自定义子类(你可以去文件 – >新build – >文件… – >cocoa触摸类。确保“子类:”是“UIView”)。

2)在初始化的时候添加一个基于xib的视图作为子视图。

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

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

在Swift 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)无论您想在故事板中使用它,只要像平常一样添加UIView,select新添加的视图,进入Identity Inspector(右上angular的第三个图标,看起来像一个带有线条的矩形),并在“自定义类”下的“类”中input您的子类名称。

(1)自动布局,(2) @IBInspectable ,和(3)sockets,我总是发现“添加它作为子视图”解决scheme不尽人意。 相反,让我给你介绍一下awakeAfter:的魔法awakeAfter: NSObject方法。

awakeAfter让你用一个完全不同的对象replace实际从NIB / Storyboard唤醒的对象。 那个对象然后通过水合过程,有awakeFromNib调用它,被添加为视图等。

我们可以在我们视图的“纸板裁剪”子类中使用它,唯一的目的是从NIB加载视图并将其返回给Storyboard使用。 然后在Storyboard视图的身份检查器中指定可embedded的子类,而不是原始类。 它实际上并不一定是一个子类,为了这个工作,但使它成为一个子类是什么让IB看到任何IBInspectable / IBOutlet属性。

注意:在NIB文件的视图上设置的类保持不变。 可embedded的子类用于故事板。 子类不能用于在代码中实例化视图,所以它本身不应该有任何额外的逻辑。 它应该包含awakeAfter挂钩。

 class MyCustomEmbeddableView: MyCustomView { override func awakeAfter(using aDecoder: NSCoder) -> Any? { return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any } } 

instantiateViewFromNibUIViewtypes安全的扩展。 它所做的只是遍历NIB的对象,直到find与该types匹配的对象。 请注意,通用types是返回值,所以types必须在呼叫站点指定。

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

目前最好的解决scheme就是使用自定义的视图控制器,在xib中定义其视图,并简单地删除xcode在向其添加vc时创build的“视图”属性(不要忘记设置自定义类虽然)。

这将使运行时自动查找xib并加载它。 您可以使用这个技巧来处理任何types的容器视图或内容视图。

即使您的class级与XIB名称不同,也可以使用此解决scheme。 例如,如果您有一个基本视图控制器类controllerA,它具有一个XIB名称controllerA.xib,并将其与子控制器B分开,并且想要在故事板中创build一个controllerB实例,则可以:

  • 在故事板中创build视图控制器
  • 将控制器的类别设置为控制器B.
  • 删除故事板中的控制器B的视图
  • 将控制器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]; } } 

我同意克里斯托弗Swasey的方法 。 这是我在几个小时的研究中发现的最好的! 我用swift 4协议添加了一些改进。 阅读他的文章,了解这个解决scheme是什么/为什么,但在这里我改编:

协议

UnXib协议应该由协议说明中描述的任何.xib类的子类实现。

 /** Use this protocol on a subclass of the class you implement in a .xib file. The subclass should be @IBDesignable have ONLY three declarations in it: * A specified **SuperClass** (ie `typealias SuperClass = MyCustomXibView`) * Override **awakeAfter(using aDecoder: NSCoder)** and return `loadNib()` * Override **prepareForInterfaceBuilder()** and call `prepareForInterfaceBuilder()` * Optionally, specify **nibName** if the .xib file has a different filename than the associated class name. (ie don't specify if `MyCustomClass.xib` contains a view of type `MyCustomClass`) */ protocol UnXib where Self: UIView { /** See `UnXib` description. */ associatedtype SuperClass: UIView var nibName: String { get } func loadNib() -> Any? func prepareForInterfaceBuilder() } extension UnXib { /** If your .xib file is not the same name as its corresponding class, then override this variable to return the correct file name. */ var nibName: String { let superClassName = String(describing: SuperClass.self) return superClassName } /** Return this method in `awakeAfter(using aDecoder: NSCoder)` to switch the dummy subclass's view with the superclass's nib */ func loadNib() -> Any? { let bundle = Bundle(for: SuperClass.self) return bundle.loadNibNamed(nibName, owner: nil, options: nil)?.first(where: { $0 is SuperClass }) } /** Makes it so your .xib file is drawn in storyboards that use it. Call this method in `prepareForInterfaceBuilder()` to draw the view in Storyboards. NOTE: Make sure the class that uses this method has `@IBDesignable` before the class declaration. */ func prepareSubclassForInterfaceBuilder() { guard let view = loadNib() as? SuperClass else { return } view.frame = self.bounds view.autoresizingMask = [.flexibleWidth, .flexibleHeight] addSubview(view) } } 

实施

然后你有你的花式自定义类(我的.xib文件被称为MyCustomView.xib ,否则我将不得不指定自定义nibNamevariables):

 class MyCustomView: UIView { @IBOutlet weak var label1: UILabel! @IBOutlet weak var label2: UILabel! } 

最后,你有这样一个子类,它可以在故事板中使用所有的drawing魔法,并且可以适当和高效地重用类.xib文件(注意@IBDesignable ):

 @IBDesignable class MyCustomViewStoryboardSubclass: MyCustomView, UnXib { typealias SuperClass = MyCustomView override func awakeAfter(using aDecoder: NSCoder) -> Any? { return loadNib() } override func prepareForInterfaceBuilder() { prepareSubclassForInterfaceBuilder() } } 

在不同的子类之间唯一会改变的是typealias SuperClass = MyCustomView ,它将反映子类的实际超类。 除此之外,这是一个样板复制/粘贴实施。 我必须说我反对复制/粘贴代码,但这是我能find的上述问题的最佳答案。

正如你所看到的,这种方法利用了IBDesignable (这是超级好的)来让你的.xib文件在你引用它的故事板上绘制自己。 这个问题不是特别提到的,而是密切相关的,许多人在看这个问题也可能会想。 看到IBDesignable这个伟大的概述了解到底是什么。